@luisrodrigues/nestjs-scheduler-dashboard 0.0.1 → 0.0.3

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 ADDED
@@ -0,0 +1,266 @@
1
+ # @luisrodrigues/nestjs-scheduler-dashboard
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
+ ---
6
+
7
+ ## Features
8
+
9
+ - Embedded UI served directly from your NestJS app (no separate frontend server)
10
+ - Execution history per job with status, duration, and error details
11
+ - Persistent metrics: total runs, failed runs, average duration — independent of history retention
12
+ - Manual job triggering and execution stop from the UI
13
+ - Concurrency control: limit how many jobs run simultaneously with a queue
14
+ - No-overlap mode: skip or queue a job if it is already running
15
+ - Optional HTTP Basic Auth to protect the dashboard
16
+ - Light / dark mode
17
+ - Standalone server mode (separate port) or embedded in your existing app
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @luisrodrigues/nestjs-scheduler-dashboard
25
+ # or
26
+ pnpm add @luisrodrigues/nestjs-scheduler-dashboard
27
+ ```
28
+
29
+ **Peer dependencies** (install if not already present):
30
+
31
+ ```bash
32
+ npm install @nestjs/common @nestjs/schedule
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Quick start
38
+
39
+ ### 1. Initialize the dashboard in `main.ts`
40
+
41
+ ```ts
42
+ import { NestFactory } from '@nestjs/core';
43
+ import { AppModule } from './app.module';
44
+ import { setupSchedulerDash } from '@luisrodrigues/nestjs-scheduler-dashboard';
45
+
46
+ async function bootstrap() {
47
+ const app = await NestFactory.create(AppModule);
48
+
49
+ await setupSchedulerDash(app);
50
+
51
+ await app.listen(3000);
52
+ }
53
+
54
+ bootstrap();
55
+ ```
56
+
57
+ The dashboard is now available at `http://localhost:3000/_jobs`.
58
+
59
+ ### 2. Decorate your jobs
60
+
61
+ Replace `@Cron` with `@TrackJob` — it accepts the same arguments:
62
+
63
+ ```ts
64
+ import { Injectable } from '@nestjs/common';
65
+ import { CronExpression } from '@nestjs/schedule';
66
+ import { TrackJob } from '@luisrodrigues/nestjs-scheduler-dashboard';
67
+
68
+ @Injectable()
69
+ export class ReportJob {
70
+ @TrackJob(CronExpression.EVERY_HOUR, { name: 'generate-report' })
71
+ async run() {
72
+ // your job logic
73
+ }
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Configuration
80
+
81
+ `setupSchedulerDash(app, options?)` accepts the following options:
82
+
83
+ | Option | Type | Default | Description |
84
+ |---|---|---|---|
85
+ | `storage` | `Storage` | `new MemoryStorage()` | Storage backend for execution history and metrics |
86
+ | `basePath` | `string` | `'_jobs'` | URL path where the dashboard is mounted |
87
+ | `port` | `number` | — | When set, serves the dashboard on a separate HTTP server instead of mounting on the main app |
88
+ | `maxConcurrent` | `number` | — | Maximum number of `@TrackJob` jobs that can run at the same time. Excess jobs are queued |
89
+ | `noOverlap` | `boolean` | `false` | Globally prevent a job from starting if it is already running |
90
+ | `auth` | `{ username, password }` | — | Protect all dashboard routes with HTTP Basic Auth |
91
+
92
+ ### `storage`
93
+
94
+ The storage backend persists execution history and metrics. The built-in `MemoryStorage` keeps everything in-process.
95
+
96
+ ```ts
97
+ import { MemoryStorage } from '@luisrodrigues/nestjs-scheduler-dashboard';
98
+
99
+ await setupSchedulerDash(app, {
100
+ storage: new MemoryStorage({
101
+ historyRetention: 50, // keep the last 50 executions per job (default: unlimited)
102
+ }),
103
+ });
104
+ ```
105
+
106
+ > **Metrics are independent of `historyRetention`.** Even if old history entries are removed, the counters for total runs, failed runs, and average duration keep accumulating.
107
+
108
+ To use a custom storage backend, extend the abstract `Storage` class:
109
+
110
+ ```ts
111
+ import { Storage, IStorageOptions, JobExecution, JobMetrics } from '@luisrodrigues/nestjs-scheduler-dashboard';
112
+
113
+ export class RedisStorage extends Storage {
114
+ constructor(options: IStorageOptions = {}) {
115
+ super(options);
116
+ }
117
+
118
+ save(execution: JobExecution): void { /* ... */ }
119
+ update(id: string, data: Partial<Pick<JobExecution, 'finishedAt' | 'status' | 'error'>>): void { /* ... */ }
120
+ findByJob(jobName: string): JobExecution[] { /* ... */ }
121
+ findAll(): Record<string, JobExecution[]> { /* ... */ }
122
+ getMetrics(jobName: string): JobMetrics { /* ... */ }
123
+ getAllMetrics(): Record<string, JobMetrics> { /* ... */ }
124
+ }
125
+ ```
126
+
127
+ ### `basePath`
128
+
129
+ Changes the URL where the dashboard is served. Must contain only alphanumeric characters, hyphens, underscores, or slashes.
130
+
131
+ ```ts
132
+ await setupSchedulerDash(app, {
133
+ basePath: 'admin/scheduler',
134
+ // dashboard → http://localhost:3000/admin/scheduler
135
+ });
136
+ ```
137
+
138
+ ### `port`
139
+
140
+ Serve the dashboard on a completely separate HTTP server, isolated from your main application.
141
+
142
+ ```ts
143
+ await setupSchedulerDash(app, {
144
+ port: 3001,
145
+ // dashboard → http://localhost:3001/_jobs
146
+ // main app → http://localhost:3000
147
+ });
148
+ ```
149
+
150
+ ### `maxConcurrent`
151
+
152
+ Limits the total number of `@TrackJob` jobs that can execute simultaneously across your entire application. Jobs that cannot start immediately are saved to storage with status `"queued"` and run in FIFO order as slots free up.
153
+
154
+ ```ts
155
+ await setupSchedulerDash(app, {
156
+ maxConcurrent: 5, // at most 5 jobs running at the same time
157
+ });
158
+ ```
159
+
160
+ ### `noOverlap`
161
+
162
+ Prevents a job from firing again if the same job is still running. Applies globally to all `@TrackJob`-decorated methods.
163
+
164
+ ```ts
165
+ await setupSchedulerDash(app, {
166
+ noOverlap: true,
167
+ });
168
+ ```
169
+
170
+ Can also be set per job via the decorator, which takes precedence over the global setting:
171
+
172
+ ```ts
173
+ @TrackJob(CronExpression.EVERY_MINUTE, { name: 'sync', noOverlap: true })
174
+ async sync() { /* ... */ }
175
+ ```
176
+
177
+ ### `auth`
178
+
179
+ Protect the dashboard with HTTP Basic Auth.
180
+
181
+ ```ts
182
+ await setupSchedulerDash(app, {
183
+ auth: {
184
+ username: 'admin',
185
+ password: 'supersecret',
186
+ },
187
+ });
188
+ ```
189
+
190
+ ---
191
+
192
+ ## `@TrackJob` decorator
193
+
194
+ `@TrackJob` is a drop-in replacement for `@Cron` from `@nestjs/schedule`. It accepts all the same options plus `noOverlap`.
195
+
196
+ ```ts
197
+ @TrackJob(cronTime, options?)
198
+ ```
199
+
200
+ | Argument | Type | Description |
201
+ |---|---|---|
202
+ | `cronTime` | `string \| CronExpression` | Cron expression or `CronExpression` enum value |
203
+ | `options.name` | `string` | Human-readable job name shown in the dashboard. Defaults to `ClassName.methodName` |
204
+ | `options.noOverlap` | `boolean` | Skip this job if it is already running. Overrides the global `noOverlap` setting |
205
+ | `options.*` | — | All other [`CronOptions`](https://docs.nestjs.com/techniques/task-scheduling) from `@nestjs/schedule` are passed through |
206
+
207
+ ```ts
208
+ @TrackJob('0 */6 * * *', {
209
+ name: 'cleanup-old-records',
210
+ noOverlap: true,
211
+ timeZone: 'Europe/Lisbon',
212
+ })
213
+ async cleanup() {
214
+ // runs every 6 hours, skipped if previous run is still in progress
215
+ }
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Full configuration example
221
+
222
+ ```ts
223
+ import { NestFactory } from '@nestjs/core';
224
+ import { AppModule } from './app.module';
225
+ import {
226
+ setupSchedulerDash,
227
+ MemoryStorage,
228
+ } from '@luisrodrigues/nestjs-scheduler-dashboard';
229
+
230
+ async function bootstrap() {
231
+ const app = await NestFactory.create(AppModule);
232
+
233
+ await setupSchedulerDash(app, {
234
+ storage: new MemoryStorage({ historyRetention: 100 }),
235
+ basePath: 'scheduler',
236
+ maxConcurrent: 3,
237
+ noOverlap: true,
238
+ auth: {
239
+ username: process.env.DASH_USER ?? 'admin',
240
+ password: process.env.DASH_PASS ?? 'secret',
241
+ },
242
+ });
243
+
244
+ await app.listen(3000);
245
+ }
246
+
247
+ bootstrap();
248
+ ```
249
+
250
+ ---
251
+
252
+ ## API
253
+
254
+ The dashboard exposes a small REST API used by the UI. You can also call it directly.
255
+
256
+ | Method | Path | Description |
257
+ |---|---|---|
258
+ | `GET` | `/{basePath}/api` | Returns all jobs with history and metrics |
259
+ | `POST` | `/{basePath}/api/:name/trigger` | Manually trigger a cron job by name |
260
+ | `POST` | `/{basePath}/api/executions/:id/stop` | Stop a running or queued execution by ID |
261
+
262
+ ---
263
+
264
+ ## License
265
+
266
+ MIT
@@ -55,7 +55,7 @@ function queueExecution(instance, args, jobName, noOverlap, original) {
55
55
  function TrackJob(cronTime, options) {
56
56
  return (target, propertyKey, descriptor) => {
57
57
  const original = descriptor.value;
58
- const jobName = options?.name ?? String(propertyKey);
58
+ const jobName = options?.name ?? `${target.constructor.name}.${String(propertyKey)}`;
59
59
  const jobNoOverlap = options?.noOverlap;
60
60
  descriptor.value = async function (...args) {
61
61
  const noOverlap = resolveNoOverlap(jobNoOverlap);
@@ -13,7 +13,7 @@ function registerUiRoutes(expressApp, base, guard) {
13
13
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
14
14
  res.send(html);
15
15
  });
16
- expressApp.get(`${base}/jobs/*`, guard, (_req, res) => {
16
+ expressApp.get(`${base}/jobs/*path`, guard, (_req, res) => {
17
17
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
18
18
  res.send(html);
19
19
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luisrodrigues/nestjs-scheduler-dashboard",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Dashboard module for @nestjs/schedule with embedded UI",
5
5
  "main": "dist/index.js",
6
6
  "publishConfig": {