@luisrodrigues/nestjs-scheduler-dashboard 0.0.6 → 0.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 +67 -53
- package/dist/index.d.ts +1 -3
- package/dist/index.js +3 -36
- package/dist/jobs.service.js +24 -2
- package/dist/public/assets/index-Boe9HKvT.js +234 -0
- package/{ui → dist/public}/index.html +2 -2
- package/dist/public/public/assets/index-BVFtTwJJ.css +1 -0
- package/dist/public/public/assets/index-Boe9HKvT.js +234 -0
- package/dist/public/public/assets/index-DmY-xViB.js +234 -0
- package/dist/public/public/assets/index-cxgVvG8b.js +234 -0
- package/dist/public/public/assets/index-vJzJcDsD.css +1 -0
- package/dist/public/public/index.html +21 -0
- package/dist/scheduler-dash-root.module.d.ts +21 -0
- package/dist/scheduler-dash-root.module.js +191 -0
- package/dist/scheduler-dash.constants.d.ts +2 -0
- package/dist/scheduler-dash.constants.js +5 -0
- package/dist/scheduler-dash.module.d.ts +14 -0
- package/dist/scheduler-dash.module.js +108 -0
- package/dist/scheduler-dash.options.d.ts +1 -1
- package/dist/scheduler-dash.schema.d.ts +1 -1
- package/dist/scheduler-dash.schema.js +3 -5
- package/package.json +10 -7
- package/ui/assets/index-6LGPlcL_.js +0 -234
- /package/{ui → dist/public}/assets/index-BVFtTwJJ.css +0 -0
package/README.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# @luisrodrigues/nestjs-scheduler-dashboard
|
|
2
2
|
|
|
3
|
-
A plug-and-play dashboard for [`@nestjs/schedule`](https://docs.nestjs.com/techniques/task-scheduling). Visualize cron job executions, track history and metrics, trigger jobs manually, and stop running executions — all from an embedded UI
|
|
4
|
-
|
|
5
|
-
The dashboard runs on its own dedicated port (default **3636**), completely isolated from your main application.
|
|
3
|
+
A plug-and-play dashboard for [`@nestjs/schedule`](https://docs.nestjs.com/techniques/task-scheduling). Visualize cron job executions, track history and metrics, trigger jobs manually, and stop running executions — all from an embedded UI served on your existing application port.
|
|
6
4
|
|
|
7
5
|
---
|
|
8
6
|
|
|
9
7
|
## Features
|
|
10
8
|
|
|
9
|
+
- Mounted directly on your app — no separate port or process
|
|
11
10
|
- Execution history per job with status, duration, and error details
|
|
12
11
|
- Persistent metrics: total runs, failed runs, average duration — independent of history retention
|
|
13
12
|
- Manual job triggering and execution stop from the UI
|
|
@@ -15,7 +14,6 @@ The dashboard runs on its own dedicated port (default **3636**), completely isol
|
|
|
15
14
|
- No-overlap mode: skip a job if it is already running
|
|
16
15
|
- Optional HTTP Basic Auth to protect the dashboard
|
|
17
16
|
- Light / dark mode
|
|
18
|
-
- Zero external runtime dependencies — served from a single self-contained HTML file
|
|
19
17
|
|
|
20
18
|
---
|
|
21
19
|
|
|
@@ -30,32 +28,27 @@ pnpm add @luisrodrigues/nestjs-scheduler-dashboard
|
|
|
30
28
|
**Peer dependencies** (install if not already present):
|
|
31
29
|
|
|
32
30
|
```bash
|
|
33
|
-
npm install @nestjs/common @nestjs/core @nestjs/schedule
|
|
31
|
+
npm install @nestjs/common @nestjs/core @nestjs/schedule express
|
|
34
32
|
```
|
|
35
33
|
|
|
36
34
|
---
|
|
37
35
|
|
|
38
36
|
## Quick start
|
|
39
37
|
|
|
40
|
-
### 1.
|
|
38
|
+
### 1. Import `SchedulerDashModule` in your `AppModule`
|
|
41
39
|
|
|
42
40
|
```ts
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log('App running at http://localhost:3000');
|
|
55
|
-
console.log('Dashboard at http://localhost:3636');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
bootstrap();
|
|
41
|
+
import { Module } from '@nestjs/common';
|
|
42
|
+
import { ScheduleModule } from '@nestjs/schedule';
|
|
43
|
+
import { SchedulerDashModule } from '@luisrodrigues/nestjs-scheduler-dashboard';
|
|
44
|
+
|
|
45
|
+
@Module({
|
|
46
|
+
imports: [
|
|
47
|
+
ScheduleModule.forRoot(),
|
|
48
|
+
SchedulerDashModule.forRoot({ route: '_scheduler' }),
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
export class AppModule {}
|
|
59
52
|
```
|
|
60
53
|
|
|
61
54
|
### 2. Decorate your jobs
|
|
@@ -76,22 +69,31 @@ export class ReportJob {
|
|
|
76
69
|
}
|
|
77
70
|
```
|
|
78
71
|
|
|
79
|
-
That's it.
|
|
72
|
+
That's it. Start your app and open `http://localhost:3000/_scheduler`.
|
|
80
73
|
|
|
81
74
|
---
|
|
82
75
|
|
|
83
76
|
## Configuration
|
|
84
77
|
|
|
85
|
-
`
|
|
78
|
+
`SchedulerDashModule.forRoot(options?)` accepts:
|
|
86
79
|
|
|
87
80
|
| Option | Type | Default | Description |
|
|
88
81
|
|---|---|---|---|
|
|
89
|
-
| `
|
|
82
|
+
| `route` | `string` | `'_scheduler'` | URL path where the dashboard is mounted |
|
|
90
83
|
| `storage` | `Storage` | `new MemoryStorage()` | Storage backend for execution history and metrics |
|
|
91
84
|
| `maxConcurrent` | `number` | — | Maximum number of jobs that can run simultaneously. Excess jobs are queued |
|
|
92
85
|
| `noOverlap` | `boolean` | `false` | Globally prevent a job from starting if it is already running |
|
|
93
86
|
| `auth` | `{ username, password }` | — | Protect the dashboard with HTTP Basic Auth |
|
|
94
87
|
|
|
88
|
+
### `route`
|
|
89
|
+
|
|
90
|
+
The URL path where the dashboard is served, relative to your app's root.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
SchedulerDashModule.forRoot({ route: 'admin/scheduler' })
|
|
94
|
+
// dashboard → http://localhost:3000/admin/scheduler
|
|
95
|
+
```
|
|
96
|
+
|
|
95
97
|
### `storage`
|
|
96
98
|
|
|
97
99
|
The default `MemoryStorage` keeps everything in-process. You can limit how many history entries are kept per job:
|
|
@@ -99,11 +101,11 @@ The default `MemoryStorage` keeps everything in-process. You can limit how many
|
|
|
99
101
|
```ts
|
|
100
102
|
import { MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
SchedulerDashModule.forRoot({
|
|
103
105
|
storage: new MemoryStorage({
|
|
104
106
|
historyRetention: 50, // keep the last 50 executions per job (default: unlimited)
|
|
105
107
|
}),
|
|
106
|
-
})
|
|
108
|
+
})
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
> **Metrics are independent of `historyRetention`.** Even after old history entries are trimmed, the counters for total runs, failed runs, and average duration keep accumulating.
|
|
@@ -132,9 +134,7 @@ export class RedisStorage extends Storage {
|
|
|
132
134
|
Limits how many `@TrackJob` jobs can run simultaneously across the entire application. Jobs that exceed the limit are saved to storage with status `"queued"` and run in FIFO order as slots free up.
|
|
133
135
|
|
|
134
136
|
```ts
|
|
135
|
-
|
|
136
|
-
maxConcurrent: 5,
|
|
137
|
-
});
|
|
137
|
+
SchedulerDashModule.forRoot({ maxConcurrent: 5 })
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
### `noOverlap`
|
|
@@ -142,12 +142,10 @@ await setupSchedulerDash(app, {
|
|
|
142
142
|
Prevents a job from firing again if it is still running. Applies globally to all `@TrackJob` methods.
|
|
143
143
|
|
|
144
144
|
```ts
|
|
145
|
-
|
|
146
|
-
noOverlap: true,
|
|
147
|
-
});
|
|
145
|
+
SchedulerDashModule.forRoot({ noOverlap: true })
|
|
148
146
|
```
|
|
149
147
|
|
|
150
|
-
Can also be overridden per job
|
|
148
|
+
Can also be overridden per job via the decorator:
|
|
151
149
|
|
|
152
150
|
```ts
|
|
153
151
|
@TrackJob(CronExpression.EVERY_MINUTE, { name: 'sync', noOverlap: true })
|
|
@@ -157,12 +155,12 @@ async sync() { /* ... */ }
|
|
|
157
155
|
### `auth`
|
|
158
156
|
|
|
159
157
|
```ts
|
|
160
|
-
|
|
158
|
+
SchedulerDashModule.forRoot({
|
|
161
159
|
auth: {
|
|
162
160
|
username: process.env.DASH_USER ?? 'admin',
|
|
163
161
|
password: process.env.DASH_PASS ?? 'secret',
|
|
164
162
|
},
|
|
165
|
-
})
|
|
163
|
+
})
|
|
166
164
|
```
|
|
167
165
|
|
|
168
166
|
---
|
|
@@ -197,38 +195,54 @@ async cleanup() {
|
|
|
197
195
|
|
|
198
196
|
## API
|
|
199
197
|
|
|
200
|
-
The dashboard exposes a small REST API
|
|
198
|
+
The dashboard exposes a small REST API under the configured route.
|
|
201
199
|
|
|
202
200
|
| Method | Path | Description |
|
|
203
201
|
|---|---|---|
|
|
204
|
-
| `GET` |
|
|
205
|
-
| `
|
|
206
|
-
| `POST` |
|
|
202
|
+
| `GET` | `/<route>/api` | Returns all jobs with history and metrics |
|
|
203
|
+
| `GET` | `/<route>/api/:name` | Returns a single job with history and metrics |
|
|
204
|
+
| `POST` | `/<route>/api/:name/trigger` | Manually trigger a cron job by name |
|
|
205
|
+
| `POST` | `/<route>/api/executions/:id/stop` | Stop a running or queued execution by ID |
|
|
207
206
|
|
|
208
207
|
---
|
|
209
208
|
|
|
210
209
|
## Full example
|
|
211
210
|
|
|
212
211
|
```ts
|
|
212
|
+
// app.module.ts
|
|
213
|
+
import { Module } from '@nestjs/common';
|
|
214
|
+
import { ScheduleModule } from '@nestjs/schedule';
|
|
215
|
+
import { SchedulerDashModule, MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
|
|
216
|
+
import { ReportJob } from './jobs/report.job';
|
|
217
|
+
|
|
218
|
+
@Module({
|
|
219
|
+
imports: [
|
|
220
|
+
ScheduleModule.forRoot(),
|
|
221
|
+
SchedulerDashModule.forRoot({
|
|
222
|
+
route: '_scheduler',
|
|
223
|
+
storage: new MemoryStorage({ historyRetention: 100 }),
|
|
224
|
+
maxConcurrent: 3,
|
|
225
|
+
noOverlap: true,
|
|
226
|
+
auth: {
|
|
227
|
+
username: process.env.DASH_USER ?? 'admin',
|
|
228
|
+
password: process.env.DASH_PASS ?? 'secret',
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
],
|
|
232
|
+
providers: [ReportJob],
|
|
233
|
+
})
|
|
234
|
+
export class AppModule {}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
// main.ts
|
|
213
239
|
import { NestFactory } from '@nestjs/core';
|
|
214
240
|
import { AppModule } from './app.module';
|
|
215
|
-
import { setupSchedulerDash, MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
|
|
216
241
|
|
|
217
242
|
async function bootstrap() {
|
|
218
243
|
const app = await NestFactory.create(AppModule);
|
|
219
|
-
|
|
220
|
-
await setupSchedulerDash(app, {
|
|
221
|
-
port: 3636,
|
|
222
|
-
storage: new MemoryStorage({ historyRetention: 100 }),
|
|
223
|
-
maxConcurrent: 3,
|
|
224
|
-
noOverlap: true,
|
|
225
|
-
auth: {
|
|
226
|
-
username: process.env.DASH_USER ?? 'admin',
|
|
227
|
-
password: process.env.DASH_PASS ?? 'secret',
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
|
|
231
244
|
await app.listen(3000);
|
|
245
|
+
// Dashboard at http://localhost:3000/_scheduler
|
|
232
246
|
}
|
|
233
247
|
|
|
234
248
|
bootstrap();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { SchedulerDashOptions } from './scheduler-dash.options';
|
|
1
|
+
export { SchedulerDashModule } from './scheduler-dash.module';
|
|
3
2
|
export { TrackJob } from './decorators/track-job.decorator';
|
|
4
3
|
export { Storage } from './storage/storage.abstract';
|
|
5
4
|
export type { IStorageOptions } from './storage/storage.abstract';
|
|
@@ -7,4 +6,3 @@ export { MemoryStorage } from './storage/memory.storage';
|
|
|
7
6
|
export type { JobExecution } from './storage/job-execution.interface';
|
|
8
7
|
export type { JobMetrics } from './storage/job-metrics.interface';
|
|
9
8
|
export type { SchedulerDashOptions, SchedulerDashAuth } from './scheduler-dash.options';
|
|
10
|
-
export declare function setupSchedulerDash(app: INestApplication, options?: SchedulerDashOptions): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,44 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.MemoryStorage = exports.Storage = exports.TrackJob = void 0;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const common_1 = require("@nestjs/common");
|
|
13
|
-
const schedule_1 = require("@nestjs/schedule");
|
|
14
|
-
const scheduler_dash_context_1 = require("./scheduler-dash.context");
|
|
15
|
-
const scheduler_dash_schema_1 = require("./scheduler-dash.schema");
|
|
16
|
-
const dashboard_module_1 = require("./dashboard.module");
|
|
17
|
-
const jobs_service_1 = require("./jobs.service");
|
|
18
|
-
const standalone_server_1 = require("./standalone-server");
|
|
3
|
+
exports.MemoryStorage = exports.Storage = exports.TrackJob = exports.SchedulerDashModule = void 0;
|
|
4
|
+
var scheduler_dash_module_1 = require("./scheduler-dash.module");
|
|
5
|
+
Object.defineProperty(exports, "SchedulerDashModule", { enumerable: true, get: function () { return scheduler_dash_module_1.SchedulerDashModule; } });
|
|
19
6
|
var track_job_decorator_1 = require("./decorators/track-job.decorator");
|
|
20
7
|
Object.defineProperty(exports, "TrackJob", { enumerable: true, get: function () { return track_job_decorator_1.TrackJob; } });
|
|
21
8
|
var storage_abstract_1 = require("./storage/storage.abstract");
|
|
22
9
|
Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return storage_abstract_1.Storage; } });
|
|
23
10
|
var memory_storage_1 = require("./storage/memory.storage");
|
|
24
11
|
Object.defineProperty(exports, "MemoryStorage", { enumerable: true, get: function () { return memory_storage_1.MemoryStorage; } });
|
|
25
|
-
const DEFAULT_PORT = 3636;
|
|
26
|
-
async function setupSchedulerDash(app, options = {}) {
|
|
27
|
-
const parsed = scheduler_dash_schema_1.SchedulerDashOptionsSchema.safeParse(options);
|
|
28
|
-
if (!parsed.success) {
|
|
29
|
-
throw new Error(`[SchedulerDash] Invalid options:\n${parsed.error.issues.map(i => ` • ${i.path.join('.')}: ${i.message}`).join('\n')}`);
|
|
30
|
-
}
|
|
31
|
-
const port = options.port ?? DEFAULT_PORT;
|
|
32
|
-
const logger = new common_1.Logger('SchedulerDashboard', { timestamp: true });
|
|
33
|
-
let _InternalDashboardApp = class _InternalDashboardApp {
|
|
34
|
-
};
|
|
35
|
-
_InternalDashboardApp = __decorate([
|
|
36
|
-
(0, common_1.Module)({ imports: [dashboard_module_1.DashboardModule.forRoot(options)] })
|
|
37
|
-
], _InternalDashboardApp);
|
|
38
|
-
await core_1.NestFactory.createApplicationContext(_InternalDashboardApp, { logger: false });
|
|
39
|
-
const storage = scheduler_dash_context_1.SchedulerDashContext.storage;
|
|
40
|
-
const schedulerRegistry = app.get(schedule_1.SchedulerRegistry);
|
|
41
|
-
const jobsService = new jobs_service_1.JobsService(schedulerRegistry, storage);
|
|
42
|
-
logger.log(`Dashboard initialized`);
|
|
43
|
-
await (0, standalone_server_1.startStandaloneServer)(port, jobsService, options.auth, logger);
|
|
44
|
-
}
|
package/dist/jobs.service.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
2
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
15
|
exports.JobsService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const schedule_1 = require("@nestjs/schedule");
|
|
18
|
+
const storage_abstract_1 = require("./storage/storage.abstract");
|
|
4
19
|
const job_concurrency_1 = require("./decorators/job-concurrency");
|
|
5
|
-
|
|
20
|
+
const scheduler_dash_constants_1 = require("./scheduler-dash.constants");
|
|
21
|
+
let JobsService = class JobsService {
|
|
6
22
|
constructor(schedulerRegistry, storage) {
|
|
7
23
|
this.schedulerRegistry = schedulerRegistry;
|
|
8
24
|
this.storage = storage;
|
|
@@ -43,5 +59,11 @@ class JobsService {
|
|
|
43
59
|
stopExecution(executionId) {
|
|
44
60
|
return (0, job_concurrency_1.stopExecutionById)(executionId);
|
|
45
61
|
}
|
|
46
|
-
}
|
|
62
|
+
};
|
|
47
63
|
exports.JobsService = JobsService;
|
|
64
|
+
exports.JobsService = JobsService = __decorate([
|
|
65
|
+
(0, common_1.Injectable)(),
|
|
66
|
+
__param(1, (0, common_1.Inject)(scheduler_dash_constants_1.STORAGE_TOKEN)),
|
|
67
|
+
__metadata("design:paramtypes", [schedule_1.SchedulerRegistry,
|
|
68
|
+
storage_abstract_1.Storage])
|
|
69
|
+
], JobsService);
|