@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 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,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. 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
54
  ### 2. Decorate your jobs
@@ -76,22 +69,31 @@ export class ReportJob {
76
69
  }
77
70
  ```
78
71
 
79
- That's it. Open `http://localhost:3636` to see the dashboard.
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
- `setupSchedulerDash(app, options?)` accepts:
78
+ `SchedulerDashModule.forRoot(options?)` accepts:
86
79
 
87
80
  | Option | Type | Default | Description |
88
81
  |---|---|---|---|
89
- | `port` | `number` | `3636` | Port for the dashboard HTTP server |
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
- await setupSchedulerDash(app, {
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
- await setupSchedulerDash(app, {
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
- await setupSchedulerDash(app, {
146
- noOverlap: true,
147
- });
145
+ SchedulerDashModule.forRoot({ noOverlap: true })
148
146
  ```
149
147
 
150
- Can also be overridden per job in the decorator:
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
- await setupSchedulerDash(app, {
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 on the same port as the UI.
198
+ The dashboard exposes a small REST API under the configured route.
201
199
 
202
200
  | Method | Path | Description |
203
201
  |---|---|---|
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 |
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
- 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';
@@ -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
- 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);