@luisrodrigues/nestjs-scheduler-dashboard 0.0.6 → 0.1.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/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 with zero external dependencies.
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,37 +28,34 @@ 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. Call `setupSchedulerDash` in `main.ts`
38
+ ### 1. Import `SchedulerDashModule` in your `AppModule`
41
39
 
42
40
  ```ts
43
- import { NestFactory } from '@nestjs/core';
44
- import { AppModule } from './app.module';
45
- import { setupSchedulerDash } from '@luisrodrigues/nestjs-scheduler-dashboard';
46
-
47
- async function bootstrap() {
48
- const app = await NestFactory.create(AppModule);
49
-
50
- // Must be called BEFORE app.listen() so storage is ready before cron jobs start
51
- await setupSchedulerDash(app, { port: 3636 });
52
-
53
- await app.listen(3000);
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
- ### 2. Decorate your jobs
54
+ ### 2. Replace `@Cron` with `@TrackJob` on every job
55
+
56
+ `@TrackJob` is a wrapper around NestJS's `@Cron` decorator. It registers the cron schedule exactly as `@Cron` does, and additionally records every execution (start time, duration, status, errors) so the dashboard can display them.
62
57
 
63
- Replace `@Cron` with `@TrackJob` it accepts the same arguments:
58
+ > **Jobs decorated with `@Cron` instead of `@TrackJob` will still run on schedule but will not appear in the dashboard history.**
64
59
 
65
60
  ```ts
66
61
  import { Injectable } from '@nestjs/common';
@@ -69,29 +64,39 @@ import { TrackJob } from '@luisrodrigues/nestjs-scheduler-dashboard';
69
64
 
70
65
  @Injectable()
71
66
  export class ReportJob {
67
+ // Before: @Cron(CronExpression.EVERY_HOUR)
72
68
  @TrackJob(CronExpression.EVERY_HOUR, { name: 'generate-report' })
73
69
  async run() {
74
- // your job logic
70
+ // your job logic — no other changes needed
75
71
  }
76
72
  }
77
73
  ```
78
74
 
79
- That's it. Open `http://localhost:3636` to see the dashboard.
75
+ That's it. Start your app and open `http://localhost:3000/_scheduler`.
80
76
 
81
77
  ---
82
78
 
83
79
  ## Configuration
84
80
 
85
- `setupSchedulerDash(app, options?)` accepts:
81
+ `SchedulerDashModule.forRoot(options?)` accepts:
86
82
 
87
83
  | Option | Type | Default | Description |
88
84
  |---|---|---|---|
89
- | `port` | `number` | `3636` | Port for the dashboard HTTP server |
85
+ | `route` | `string` | `'_scheduler'` | URL path where the dashboard is mounted |
90
86
  | `storage` | `Storage` | `new MemoryStorage()` | Storage backend for execution history and metrics |
91
87
  | `maxConcurrent` | `number` | — | Maximum number of jobs that can run simultaneously. Excess jobs are queued |
92
88
  | `noOverlap` | `boolean` | `false` | Globally prevent a job from starting if it is already running |
93
89
  | `auth` | `{ username, password }` | — | Protect the dashboard with HTTP Basic Auth |
94
90
 
91
+ ### `route`
92
+
93
+ The URL path where the dashboard is served, relative to your app's root.
94
+
95
+ ```ts
96
+ SchedulerDashModule.forRoot({ route: 'admin/scheduler' })
97
+ // dashboard → http://localhost:3000/admin/scheduler
98
+ ```
99
+
95
100
  ### `storage`
96
101
 
97
102
  The default `MemoryStorage` keeps everything in-process. You can limit how many history entries are kept per job:
@@ -99,11 +104,11 @@ The default `MemoryStorage` keeps everything in-process. You can limit how many
99
104
  ```ts
100
105
  import { MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
101
106
 
102
- await setupSchedulerDash(app, {
107
+ SchedulerDashModule.forRoot({
103
108
  storage: new MemoryStorage({
104
109
  historyRetention: 50, // keep the last 50 executions per job (default: unlimited)
105
110
  }),
106
- });
111
+ })
107
112
  ```
108
113
 
109
114
  > **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 +137,7 @@ export class RedisStorage extends Storage {
132
137
  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
138
 
134
139
  ```ts
135
- await setupSchedulerDash(app, {
136
- maxConcurrent: 5,
137
- });
140
+ SchedulerDashModule.forRoot({ maxConcurrent: 5 })
138
141
  ```
139
142
 
140
143
  ### `noOverlap`
@@ -142,12 +145,10 @@ await setupSchedulerDash(app, {
142
145
  Prevents a job from firing again if it is still running. Applies globally to all `@TrackJob` methods.
143
146
 
144
147
  ```ts
145
- await setupSchedulerDash(app, {
146
- noOverlap: true,
147
- });
148
+ SchedulerDashModule.forRoot({ noOverlap: true })
148
149
  ```
149
150
 
150
- Can also be overridden per job in the decorator:
151
+ Can also be overridden per job via the decorator:
151
152
 
152
153
  ```ts
153
154
  @TrackJob(CronExpression.EVERY_MINUTE, { name: 'sync', noOverlap: true })
@@ -157,19 +158,21 @@ async sync() { /* ... */ }
157
158
  ### `auth`
158
159
 
159
160
  ```ts
160
- await setupSchedulerDash(app, {
161
+ SchedulerDashModule.forRoot({
161
162
  auth: {
162
163
  username: process.env.DASH_USER ?? 'admin',
163
164
  password: process.env.DASH_PASS ?? 'secret',
164
165
  },
165
- });
166
+ })
166
167
  ```
167
168
 
168
169
  ---
169
170
 
170
171
  ## `@TrackJob` decorator
171
172
 
172
- Drop-in replacement for `@Cron`. Accepts all the same options plus `noOverlap`.
173
+ `@TrackJob` is a **wrapper around `@Cron`** from `@nestjs/schedule`. It accepts every argument that `@Cron` accepts and passes them through unchanged, so migrating an existing job is a one-line change. The only addition is the `noOverlap` option and the automatic execution tracking.
174
+
175
+ **Every scheduled job must use `@TrackJob` instead of `@Cron`.** Jobs that remain on `@Cron` will run normally but their executions will not be recorded or visible in the dashboard.
173
176
 
174
177
  ```ts
175
178
  @TrackJob(cronTime, options?)
@@ -197,38 +200,54 @@ async cleanup() {
197
200
 
198
201
  ## API
199
202
 
200
- The dashboard exposes a small REST API on the same port as the UI.
203
+ The dashboard exposes a small REST API under the configured route.
201
204
 
202
205
  | Method | Path | Description |
203
206
  |---|---|---|
204
- | `GET` | `/api` | Returns all jobs with history and metrics |
205
- | `POST` | `/api/:name/trigger` | Manually trigger a cron job by name |
206
- | `POST` | `/api/executions/:id/stop` | Stop a running or queued execution by ID |
207
+ | `GET` | `/<route>/api` | Returns all jobs with history and metrics |
208
+ | `GET` | `/<route>/api/:name` | Returns a single job with history and metrics |
209
+ | `POST` | `/<route>/api/:name/trigger` | Manually trigger a cron job by name |
210
+ | `POST` | `/<route>/api/executions/:id/stop` | Stop a running or queued execution by ID |
207
211
 
208
212
  ---
209
213
 
210
214
  ## Full example
211
215
 
212
216
  ```ts
217
+ // app.module.ts
218
+ import { Module } from '@nestjs/common';
219
+ import { ScheduleModule } from '@nestjs/schedule';
220
+ import { SchedulerDashModule, MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
221
+ import { ReportJob } from './jobs/report.job';
222
+
223
+ @Module({
224
+ imports: [
225
+ ScheduleModule.forRoot(),
226
+ SchedulerDashModule.forRoot({
227
+ route: '_scheduler',
228
+ storage: new MemoryStorage({ historyRetention: 100 }),
229
+ maxConcurrent: 3,
230
+ noOverlap: true,
231
+ auth: {
232
+ username: process.env.DASH_USER ?? 'admin',
233
+ password: process.env.DASH_PASS ?? 'secret',
234
+ },
235
+ }),
236
+ ],
237
+ providers: [ReportJob],
238
+ })
239
+ export class AppModule {}
240
+ ```
241
+
242
+ ```ts
243
+ // main.ts
213
244
  import { NestFactory } from '@nestjs/core';
214
245
  import { AppModule } from './app.module';
215
- import { setupSchedulerDash, MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
216
246
 
217
247
  async function bootstrap() {
218
248
  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
249
  await app.listen(3000);
250
+ // Dashboard at http://localhost:3000/_scheduler
232
251
  }
233
252
 
234
253
  bootstrap();
package/dist/index.d.ts CHANGED
@@ -1,10 +1,8 @@
1
- import type { INestApplication } from '@nestjs/common';
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';
6
5
  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
- export type { SchedulerDashOptions, SchedulerDashAuth } from './scheduler-dash.options';
10
- export declare function setupSchedulerDash(app: INestApplication, options?: SchedulerDashOptions): Promise<void>;
8
+ export type { SchedulerDashOptions, SchedulerDashAuth, SchedulerDashAsyncOptions } from './scheduler-dash.options';
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
- exports.setupSchedulerDash = setupSchedulerDash;
11
- const core_1 = require("@nestjs/core");
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
- }
@@ -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
- class JobsService {
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);