@luisrodrigues/nestjs-scheduler-dashboard 0.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.
Files changed (41) hide show
  1. package/dist/auth.d.ts +5 -0
  2. package/dist/auth.js +31 -0
  3. package/dist/basic-auth.guard.d.ts +7 -0
  4. package/dist/basic-auth.guard.js +44 -0
  5. package/dist/dashboard.server.d.ts +4 -0
  6. package/dist/dashboard.server.js +63 -0
  7. package/dist/decorators/job-concurrency.d.ts +22 -0
  8. package/dist/decorators/job-concurrency.js +108 -0
  9. package/dist/decorators/track-job.decorator.d.ts +6 -0
  10. package/dist/decorators/track-job.decorator.js +77 -0
  11. package/dist/embedded-server.d.ts +4 -0
  12. package/dist/embedded-server.js +47 -0
  13. package/dist/index.d.ts +10 -0
  14. package/dist/index.js +41 -0
  15. package/dist/jobs-tracker.service.d.ts +9 -0
  16. package/dist/jobs-tracker.service.js +40 -0
  17. package/dist/jobs.controller.d.ts +25 -0
  18. package/dist/jobs.controller.js +72 -0
  19. package/dist/jobs.service.d.ts +25 -0
  20. package/dist/jobs.service.js +34 -0
  21. package/dist/scheduler-dash.context.d.ts +7 -0
  22. package/dist/scheduler-dash.context.js +10 -0
  23. package/dist/scheduler-dash.module.d.ts +5 -0
  24. package/dist/scheduler-dash.module.js +39 -0
  25. package/dist/scheduler-dash.options.d.ts +13 -0
  26. package/dist/scheduler-dash.options.js +2 -0
  27. package/dist/scheduler-dash.schema.d.ts +17 -0
  28. package/dist/scheduler-dash.schema.js +29 -0
  29. package/dist/standalone-server.d.ts +5 -0
  30. package/dist/standalone-server.js +70 -0
  31. package/dist/storage/job-execution.interface.d.ts +8 -0
  32. package/dist/storage/job-execution.interface.js +2 -0
  33. package/dist/storage/job-metrics.interface.d.ts +5 -0
  34. package/dist/storage/job-metrics.interface.js +2 -0
  35. package/dist/storage/memory.storage.d.ts +16 -0
  36. package/dist/storage/memory.storage.js +71 -0
  37. package/dist/storage/storage.abstract.d.ts +15 -0
  38. package/dist/storage/storage.abstract.js +9 -0
  39. package/dist/ui/dashboard.d.ts +1 -0
  40. package/dist/ui/dashboard.js +4 -0
  41. package/package.json +46 -0
package/dist/auth.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { SchedulerDashAuth } from './scheduler-dash.options';
3
+ export declare function checkBasicAuth(req: IncomingMessage, auth: SchedulerDashAuth): boolean;
4
+ export declare function rejectUnauthorized(res: ServerResponse): void;
5
+ export declare function createAuthGuard(auth: SchedulerDashAuth | undefined): (req: any, res: any, next: any) => any;
package/dist/auth.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkBasicAuth = checkBasicAuth;
4
+ exports.rejectUnauthorized = rejectUnauthorized;
5
+ exports.createAuthGuard = createAuthGuard;
6
+ function checkBasicAuth(req, auth) {
7
+ const header = req.headers['authorization'] ?? '';
8
+ if (!header.startsWith('Basic '))
9
+ return false;
10
+ const decoded = Buffer.from(header.slice(6), 'base64').toString('utf-8');
11
+ const [user, ...rest] = decoded.split(':');
12
+ return user === auth.username && rest.join(':') === auth.password;
13
+ }
14
+ function rejectUnauthorized(res) {
15
+ res.writeHead(401, {
16
+ 'WWW-Authenticate': 'Basic realm="Scheduler Dashboard"',
17
+ 'Content-Type': 'text/plain',
18
+ });
19
+ res.end('Unauthorized');
20
+ }
21
+ function createAuthGuard(auth) {
22
+ return (req, res, next) => {
23
+ if (!auth)
24
+ return next();
25
+ if (!checkBasicAuth(req, auth)) {
26
+ res.setHeader('WWW-Authenticate', 'Basic realm="Scheduler Dashboard"');
27
+ return res.status(401).send('Unauthorized');
28
+ }
29
+ next();
30
+ };
31
+ }
@@ -0,0 +1,7 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { SchedulerDashOptions } from './scheduler-dash.options';
3
+ export declare class BasicAuthGuard implements CanActivate {
4
+ private readonly options;
5
+ constructor(options: SchedulerDashOptions);
6
+ canActivate(context: ExecutionContext): boolean;
7
+ }
@@ -0,0 +1,44 @@
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
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.BasicAuthGuard = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const scheduler_dash_options_1 = require("./scheduler-dash.options");
18
+ let BasicAuthGuard = class BasicAuthGuard {
19
+ constructor(options) {
20
+ this.options = options;
21
+ }
22
+ canActivate(context) {
23
+ if (!this.options.auth)
24
+ return true;
25
+ const req = context.switchToHttp().getRequest();
26
+ const res = context.switchToHttp().getResponse();
27
+ const header = req.headers['authorization'] ?? '';
28
+ if (header.startsWith('Basic ')) {
29
+ const decoded = Buffer.from(header.slice(6), 'base64').toString('utf-8');
30
+ const [user, ...rest] = decoded.split(':');
31
+ if (user === this.options.auth.username && rest.join(':') === this.options.auth.password) {
32
+ return true;
33
+ }
34
+ }
35
+ res.setHeader('WWW-Authenticate', 'Basic realm="Scheduler Dashboard"');
36
+ throw new common_1.UnauthorizedException('Invalid credentials');
37
+ }
38
+ };
39
+ exports.BasicAuthGuard = BasicAuthGuard;
40
+ exports.BasicAuthGuard = BasicAuthGuard = __decorate([
41
+ (0, common_1.Injectable)(),
42
+ __param(0, (0, common_1.Inject)(scheduler_dash_options_1.SCHEDULER_DASH_OPTIONS)),
43
+ __metadata("design:paramtypes", [Object])
44
+ ], BasicAuthGuard);
@@ -0,0 +1,4 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { SchedulerDashAuth } from './scheduler-dash.options';
3
+ import { JobsService } from './jobs.service';
4
+ export declare function startDashboardServer(port: number, jobsService: JobsService, auth?: SchedulerDashAuth): import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startDashboardServer = startDashboardServer;
4
+ const http_1 = require("http");
5
+ const dashboard_1 = require("./ui/dashboard");
6
+ function unauthorized(res) {
7
+ res.writeHead(401, {
8
+ 'WWW-Authenticate': 'Basic realm="Scheduler Dashboard"',
9
+ 'Content-Type': 'text/plain',
10
+ });
11
+ res.end('Unauthorized');
12
+ }
13
+ function json(res, status, body) {
14
+ const payload = JSON.stringify(body);
15
+ res.writeHead(status, { 'Content-Type': 'application/json' });
16
+ res.end(payload);
17
+ }
18
+ function checkAuth(req, auth) {
19
+ const header = req.headers['authorization'] ?? '';
20
+ if (!header.startsWith('Basic '))
21
+ return false;
22
+ const decoded = Buffer.from(header.slice(6), 'base64').toString('utf-8');
23
+ const [user, ...rest] = decoded.split(':');
24
+ return user === auth.username && rest.join(':') === auth.password;
25
+ }
26
+ async function readBody(req) {
27
+ return new Promise((resolve) => {
28
+ let body = '';
29
+ req.on('data', chunk => (body += chunk));
30
+ req.on('end', () => resolve(body));
31
+ });
32
+ }
33
+ function startDashboardServer(port, jobsService, auth) {
34
+ const server = (0, http_1.createServer)(async (req, res) => {
35
+ const url = req.url ?? '/';
36
+ const method = req.method ?? 'GET';
37
+ if (auth && !checkAuth(req, auth)) {
38
+ return unauthorized(res);
39
+ }
40
+ if (method === 'GET' && (url === '/' || url.startsWith('/jobs/'))) {
41
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
42
+ return res.end(dashboard_1.dashboardHtml);
43
+ }
44
+ if (method === 'GET' && url === '/api') {
45
+ return json(res, 200, jobsService.getJobs());
46
+ }
47
+ const actionMatch = url.match(/^\/api\/([^/]+)\/trigger$/);
48
+ if (method === 'POST' && actionMatch) {
49
+ await readBody(req);
50
+ const name = decodeURIComponent(actionMatch[1]);
51
+ const ok = jobsService.triggerJob(name);
52
+ if (!ok)
53
+ return json(res, 404, { message: `Job "${name}" not found` });
54
+ return json(res, 200, { triggered: name });
55
+ }
56
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
57
+ res.end('Not Found');
58
+ });
59
+ server.listen(port, () => {
60
+ console.log(`[SchedulerDash] Dashboard running at http://localhost:${port}`);
61
+ });
62
+ return server;
63
+ }
@@ -0,0 +1,22 @@
1
+ import type { Storage } from '../storage/storage.abstract';
2
+ export interface QueuedEntry {
3
+ instance: unknown;
4
+ args: unknown[];
5
+ jobName: string;
6
+ executionId: string;
7
+ noOverlap: boolean;
8
+ original: (...a: unknown[]) => unknown;
9
+ storage: Storage;
10
+ }
11
+ export declare function isOverlapping(jobName: string, noOverlap: boolean): boolean;
12
+ export declare function isConcurrencyLimitReached(): boolean;
13
+ declare function onJobStart(jobName: string): void;
14
+ declare function onJobEnd(jobName: string): void;
15
+ export declare function registerRunningExecution(executionId: string, jobName: string, storage: Storage): void;
16
+ export declare function unregisterRunningExecution(executionId: string): void;
17
+ export declare function wasExecutionStopped(executionId: string): boolean;
18
+ export declare function consumeStoppedExecution(executionId: string): boolean;
19
+ export declare function stopExecutionById(executionId: string): boolean;
20
+ export declare function enqueueEntry(entry: QueuedEntry): void;
21
+ export declare function runEntry(entry: QueuedEntry): void;
22
+ export { onJobStart, onJobEnd };
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isOverlapping = isOverlapping;
4
+ exports.isConcurrencyLimitReached = isConcurrencyLimitReached;
5
+ exports.registerRunningExecution = registerRunningExecution;
6
+ exports.unregisterRunningExecution = unregisterRunningExecution;
7
+ exports.wasExecutionStopped = wasExecutionStopped;
8
+ exports.consumeStoppedExecution = consumeStoppedExecution;
9
+ exports.stopExecutionById = stopExecutionById;
10
+ exports.enqueueEntry = enqueueEntry;
11
+ exports.runEntry = runEntry;
12
+ exports.onJobStart = onJobStart;
13
+ exports.onJobEnd = onJobEnd;
14
+ const scheduler_dash_context_1 = require("../scheduler-dash.context");
15
+ let runningCount = 0;
16
+ const runningJobs = new Set();
17
+ const queue = [];
18
+ const runningExecutions = new Map();
19
+ const stoppedExecutions = new Set();
20
+ function isOverlapping(jobName, noOverlap) {
21
+ return noOverlap && runningJobs.has(jobName);
22
+ }
23
+ function isConcurrencyLimitReached() {
24
+ const max = scheduler_dash_context_1.SchedulerDashContext.maxConcurrent;
25
+ return max !== undefined && runningCount >= max;
26
+ }
27
+ function onJobStart(jobName) {
28
+ runningCount++;
29
+ runningJobs.add(jobName);
30
+ }
31
+ function onJobEnd(jobName) {
32
+ runningCount--;
33
+ runningJobs.delete(jobName);
34
+ drainQueue();
35
+ }
36
+ function registerRunningExecution(executionId, jobName, storage) {
37
+ runningExecutions.set(executionId, { jobName, storage });
38
+ }
39
+ function unregisterRunningExecution(executionId) {
40
+ runningExecutions.delete(executionId);
41
+ }
42
+ function wasExecutionStopped(executionId) {
43
+ return stoppedExecutions.has(executionId);
44
+ }
45
+ function consumeStoppedExecution(executionId) {
46
+ return stoppedExecutions.delete(executionId);
47
+ }
48
+ function stopExecutionById(executionId) {
49
+ const queueIdx = queue.findIndex(e => e.executionId === executionId);
50
+ if (queueIdx !== -1) {
51
+ const [entry] = queue.splice(queueIdx, 1);
52
+ entry.storage.update(executionId, { finishedAt: new Date(), status: 'stopped' });
53
+ return true;
54
+ }
55
+ const running = runningExecutions.get(executionId);
56
+ if (running) {
57
+ stoppedExecutions.add(executionId);
58
+ runningExecutions.delete(executionId);
59
+ running.storage.update(executionId, { finishedAt: new Date(), status: 'stopped' });
60
+ onJobEnd(running.jobName);
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ function enqueueEntry(entry) {
66
+ queue.push(entry);
67
+ }
68
+ function nextEligibleIndex() {
69
+ return queue.findIndex(e => !e.noOverlap || !runningJobs.has(e.jobName));
70
+ }
71
+ function drainQueue() {
72
+ const max = scheduler_dash_context_1.SchedulerDashContext.maxConcurrent;
73
+ if (max === undefined)
74
+ return;
75
+ while (runningCount < max) {
76
+ const idx = nextEligibleIndex();
77
+ if (idx === -1)
78
+ break;
79
+ const [entry] = queue.splice(idx, 1);
80
+ runEntry(entry);
81
+ }
82
+ }
83
+ function formatError(err) {
84
+ return err instanceof Error ? (err.stack ?? err.message) : String(err);
85
+ }
86
+ function runEntry(entry) {
87
+ const { instance, args, jobName, executionId, original, storage } = entry;
88
+ onJobStart(jobName);
89
+ registerRunningExecution(executionId, jobName, storage);
90
+ storage.update(executionId, { status: 'running' });
91
+ Promise.resolve(original.apply(instance, args))
92
+ .then(() => {
93
+ if (!wasExecutionStopped(executionId)) {
94
+ storage.update(executionId, { finishedAt: new Date(), status: 'completed' });
95
+ }
96
+ })
97
+ .catch((err) => {
98
+ if (!wasExecutionStopped(executionId)) {
99
+ storage.update(executionId, { finishedAt: new Date(), status: 'failed', error: formatError(err) });
100
+ }
101
+ })
102
+ .finally(() => {
103
+ unregisterRunningExecution(executionId);
104
+ if (!consumeStoppedExecution(executionId)) {
105
+ onJobEnd(jobName);
106
+ }
107
+ });
108
+ }
@@ -0,0 +1,6 @@
1
+ import { Cron } from '@nestjs/schedule';
2
+ import type { CronOptions } from '@nestjs/schedule';
3
+ export type TrackJobOptions = CronOptions & {
4
+ noOverlap?: boolean;
5
+ };
6
+ export declare function TrackJob(cronTime: Parameters<typeof Cron>[0], options?: TrackJobOptions): MethodDecorator;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TrackJob = TrackJob;
4
+ const schedule_1 = require("@nestjs/schedule");
5
+ const crypto_1 = require("crypto");
6
+ const scheduler_dash_context_1 = require("../scheduler-dash.context");
7
+ const job_concurrency_1 = require("./job-concurrency");
8
+ function formatError(err) {
9
+ return err instanceof Error ? (err.stack ?? err.message) : String(err);
10
+ }
11
+ function resolveNoOverlap(jobNoOverlap) {
12
+ return jobNoOverlap ?? scheduler_dash_context_1.SchedulerDashContext.noOverlap;
13
+ }
14
+ async function runWithoutStorage(instance, args, jobName, original) {
15
+ (0, job_concurrency_1.onJobStart)(jobName);
16
+ try {
17
+ return await original.apply(instance, args);
18
+ }
19
+ finally {
20
+ (0, job_concurrency_1.onJobEnd)(jobName);
21
+ }
22
+ }
23
+ async function runImmediately(instance, args, jobName, original) {
24
+ const storage = scheduler_dash_context_1.SchedulerDashContext.storage;
25
+ const id = (0, crypto_1.randomUUID)();
26
+ storage.save({ id, jobName, startedAt: new Date(), finishedAt: null, status: 'running' });
27
+ (0, job_concurrency_1.onJobStart)(jobName);
28
+ (0, job_concurrency_1.registerRunningExecution)(id, jobName, storage);
29
+ try {
30
+ const result = await original.apply(instance, args);
31
+ if (!(0, job_concurrency_1.wasExecutionStopped)(id)) {
32
+ storage.update(id, { finishedAt: new Date(), status: 'completed' });
33
+ }
34
+ return result;
35
+ }
36
+ catch (err) {
37
+ if (!(0, job_concurrency_1.wasExecutionStopped)(id)) {
38
+ storage.update(id, { finishedAt: new Date(), status: 'failed', error: formatError(err) });
39
+ }
40
+ throw err;
41
+ }
42
+ finally {
43
+ (0, job_concurrency_1.unregisterRunningExecution)(id);
44
+ if (!(0, job_concurrency_1.consumeStoppedExecution)(id)) {
45
+ (0, job_concurrency_1.onJobEnd)(jobName);
46
+ }
47
+ }
48
+ }
49
+ function queueExecution(instance, args, jobName, noOverlap, original) {
50
+ const storage = scheduler_dash_context_1.SchedulerDashContext.storage;
51
+ const id = (0, crypto_1.randomUUID)();
52
+ storage.save({ id, jobName, startedAt: new Date(), finishedAt: null, status: 'queued' });
53
+ (0, job_concurrency_1.enqueueEntry)({ instance, args, jobName, executionId: id, noOverlap, original, storage });
54
+ }
55
+ function TrackJob(cronTime, options) {
56
+ return (target, propertyKey, descriptor) => {
57
+ const original = descriptor.value;
58
+ const jobName = options?.name ?? String(propertyKey);
59
+ const jobNoOverlap = options?.noOverlap;
60
+ descriptor.value = async function (...args) {
61
+ const noOverlap = resolveNoOverlap(jobNoOverlap);
62
+ const storage = scheduler_dash_context_1.SchedulerDashContext.storage;
63
+ if ((0, job_concurrency_1.isOverlapping)(jobName, noOverlap))
64
+ return;
65
+ if (!storage)
66
+ return runWithoutStorage(this, args, jobName, original);
67
+ if ((0, job_concurrency_1.isConcurrencyLimitReached)()) {
68
+ queueExecution(this, args, jobName, noOverlap, original);
69
+ return;
70
+ }
71
+ return runImmediately(this, args, jobName, original);
72
+ };
73
+ const { noOverlap: _, ...cronOptions } = options ?? {};
74
+ (0, schedule_1.Cron)(cronTime, { name: jobName, ...cronOptions })(target, propertyKey, descriptor);
75
+ return descriptor;
76
+ };
77
+ }
@@ -0,0 +1,4 @@
1
+ import { INestApplication, Logger } from '@nestjs/common';
2
+ import { JobsService } from './jobs.service';
3
+ import { SchedulerDashAuth } from './scheduler-dash.options';
4
+ export declare function mountOnApp(app: INestApplication, basePath: string, jobsService: JobsService, auth: SchedulerDashAuth | undefined, logger: Logger): void;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mountOnApp = mountOnApp;
4
+ const auth_1 = require("./auth");
5
+ const dashboard_1 = require("./ui/dashboard");
6
+ const PLACEHOLDER = '__SCHEDULER_BASE_PLACEHOLDER__';
7
+ function renderHtml(base) {
8
+ return dashboard_1.dashboardHtml.replace(PLACEHOLDER, base);
9
+ }
10
+ function registerUiRoutes(expressApp, base, guard) {
11
+ const html = renderHtml(base);
12
+ expressApp.get(base, guard, (_req, res) => {
13
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
14
+ res.send(html);
15
+ });
16
+ expressApp.get(`${base}/jobs/*`, guard, (_req, res) => {
17
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
18
+ res.send(html);
19
+ });
20
+ }
21
+ function registerApiRoutes(expressApp, base, guard, jobsService) {
22
+ expressApp.get(`${base}/api`, guard, (_req, res) => {
23
+ res.json(jobsService.getJobs());
24
+ });
25
+ expressApp.post(`${base}/api/:name/trigger`, guard, (req, res) => {
26
+ const name = decodeURIComponent(req.params.name);
27
+ const ok = jobsService.triggerJob(name);
28
+ if (!ok)
29
+ return res.status(404).json({ message: `Job "${name}" not found` });
30
+ res.json({ triggered: name });
31
+ });
32
+ expressApp.post(`${base}/api/executions/:id/stop`, guard, (req, res) => {
33
+ const id = decodeURIComponent(req.params.id);
34
+ const ok = jobsService.stopExecution(id);
35
+ if (!ok)
36
+ return res.status(404).json({ message: `Execution "${id}" not found or already finished` });
37
+ res.json({ stopped: id });
38
+ });
39
+ }
40
+ function mountOnApp(app, basePath, jobsService, auth, logger) {
41
+ const base = `/${basePath}`;
42
+ const expressApp = app.getHttpAdapter().getInstance();
43
+ const guard = (0, auth_1.createAuthGuard)(auth);
44
+ registerUiRoutes(expressApp, base, guard);
45
+ registerApiRoutes(expressApp, base, guard, jobsService);
46
+ logger.log(`Dashboard available at ${base}`);
47
+ }
@@ -0,0 +1,10 @@
1
+ import { INestApplication } from '@nestjs/common';
2
+ import { SchedulerDashOptions } from './scheduler-dash.options';
3
+ export { TrackJob } from './decorators/track-job.decorator';
4
+ export { Storage } from './storage/storage.abstract';
5
+ export type { IStorageOptions } from './storage/storage.abstract';
6
+ export { MemoryStorage } from './storage/memory.storage';
7
+ export type { JobExecution } from './storage/job-execution.interface';
8
+ 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>;
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryStorage = exports.Storage = exports.TrackJob = void 0;
4
+ exports.setupSchedulerDash = setupSchedulerDash;
5
+ const common_1 = require("@nestjs/common");
6
+ const schedule_1 = require("@nestjs/schedule");
7
+ const scheduler_dash_context_1 = require("./scheduler-dash.context");
8
+ const scheduler_dash_schema_1 = require("./scheduler-dash.schema");
9
+ const jobs_service_1 = require("./jobs.service");
10
+ const memory_storage_1 = require("./storage/memory.storage");
11
+ const standalone_server_1 = require("./standalone-server");
12
+ const embedded_server_1 = require("./embedded-server");
13
+ var track_job_decorator_1 = require("./decorators/track-job.decorator");
14
+ Object.defineProperty(exports, "TrackJob", { enumerable: true, get: function () { return track_job_decorator_1.TrackJob; } });
15
+ var storage_abstract_1 = require("./storage/storage.abstract");
16
+ Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return storage_abstract_1.Storage; } });
17
+ var memory_storage_2 = require("./storage/memory.storage");
18
+ Object.defineProperty(exports, "MemoryStorage", { enumerable: true, get: function () { return memory_storage_2.MemoryStorage; } });
19
+ async function setupSchedulerDash(app, options = {}) {
20
+ const parsed = scheduler_dash_schema_1.SchedulerDashOptionsSchema.safeParse(options);
21
+ if (!parsed.success) {
22
+ throw new Error(`[SchedulerDash] Invalid options:\n${parsed.error.issues.map(i => ` • ${i.path.join('.')}: ${i.message}`).join('\n')}`);
23
+ }
24
+ const storage = options.storage ?? new memory_storage_1.MemoryStorage({
25
+ historyRetention: 10
26
+ });
27
+ const basePath = (options.basePath ?? '_jobs').replace(/^\//, '');
28
+ const logger = new common_1.Logger('SchedulerDash-Dashboard', { timestamp: true });
29
+ scheduler_dash_context_1.SchedulerDashContext.storage = storage;
30
+ scheduler_dash_context_1.SchedulerDashContext.basePath = basePath;
31
+ scheduler_dash_context_1.SchedulerDashContext.noOverlap = options.noOverlap ?? false;
32
+ scheduler_dash_context_1.SchedulerDashContext.maxConcurrent = options.maxConcurrent;
33
+ const schedulerRegistry = app.get(schedule_1.SchedulerRegistry);
34
+ const jobsService = new jobs_service_1.JobsService(schedulerRegistry, storage);
35
+ if (options.port) {
36
+ (0, standalone_server_1.startStandaloneServer)(options.port, basePath, jobsService, options.auth, logger);
37
+ }
38
+ else {
39
+ (0, embedded_server_1.mountOnApp)(app, basePath, jobsService, options.auth, logger);
40
+ }
41
+ }
@@ -0,0 +1,9 @@
1
+ import { SchedulerDashOptions } from './scheduler-dash.options';
2
+ import { Storage } from './storage/storage.abstract';
3
+ export declare class JobsTrackerService {
4
+ private readonly storage;
5
+ private readonly options;
6
+ private readonly logger;
7
+ constructor(storage: Storage, options: SchedulerDashOptions);
8
+ onModuleInit(): void;
9
+ }
@@ -0,0 +1,40 @@
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
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.JobsTrackerService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const scheduler_dash_options_1 = require("./scheduler-dash.options");
18
+ const scheduler_dash_context_1 = require("./scheduler-dash.context");
19
+ const storage_abstract_1 = require("./storage/storage.abstract");
20
+ let JobsTrackerService = class JobsTrackerService {
21
+ constructor(storage, options) {
22
+ this.storage = storage;
23
+ this.options = options;
24
+ this.logger = new common_1.Logger('SchedulerDash');
25
+ }
26
+ onModuleInit() {
27
+ scheduler_dash_context_1.SchedulerDashContext.storage = this.storage;
28
+ scheduler_dash_context_1.SchedulerDashContext.basePath = this.options.basePath ?? '_jobs';
29
+ const basePath = scheduler_dash_context_1.SchedulerDashContext.basePath;
30
+ this.logger.log(`Dashboard available at /${basePath}`);
31
+ this.logger.log(`API available at /${basePath}/api`);
32
+ }
33
+ };
34
+ exports.JobsTrackerService = JobsTrackerService;
35
+ exports.JobsTrackerService = JobsTrackerService = __decorate([
36
+ (0, common_1.Injectable)(),
37
+ __param(0, (0, common_1.Inject)(scheduler_dash_options_1.SCHEDULER_DASH_STORAGE)),
38
+ __param(1, (0, common_1.Inject)(scheduler_dash_options_1.SCHEDULER_DASH_OPTIONS)),
39
+ __metadata("design:paramtypes", [storage_abstract_1.Storage, Object])
40
+ ], JobsTrackerService);
@@ -0,0 +1,25 @@
1
+ import { JobsService } from './jobs.service';
2
+ export declare class JobsController {
3
+ private readonly jobsService;
4
+ constructor(jobsService: JobsService);
5
+ dashboard(): string;
6
+ dashboardDetail(): string;
7
+ getJobs(): {
8
+ cron: {
9
+ name: string;
10
+ cronExpression: any;
11
+ running: boolean;
12
+ nextRun: string;
13
+ history: import(".").JobExecution[];
14
+ }[];
15
+ intervals: {
16
+ name: string;
17
+ }[];
18
+ timeouts: {
19
+ name: string;
20
+ }[];
21
+ };
22
+ triggerJob(name: string): {
23
+ triggered: string;
24
+ };
25
+ }
@@ -0,0 +1,72 @@
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
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.JobsController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const basic_auth_guard_1 = require("./basic-auth.guard");
18
+ const jobs_service_1 = require("./jobs.service");
19
+ const dashboard_1 = require("./ui/dashboard");
20
+ let JobsController = class JobsController {
21
+ constructor(jobsService) {
22
+ this.jobsService = jobsService;
23
+ }
24
+ dashboard() {
25
+ return dashboard_1.dashboardHtml;
26
+ }
27
+ dashboardDetail() {
28
+ return dashboard_1.dashboardHtml;
29
+ }
30
+ getJobs() {
31
+ return this.jobsService.getJobs();
32
+ }
33
+ triggerJob(name) {
34
+ const ok = this.jobsService.triggerJob(name);
35
+ if (!ok)
36
+ throw new common_1.NotFoundException(`Job "${name}" not found`);
37
+ return { triggered: name };
38
+ }
39
+ };
40
+ exports.JobsController = JobsController;
41
+ __decorate([
42
+ (0, common_1.Get)(),
43
+ (0, common_1.Header)('Content-Type', 'text/html; charset=utf-8'),
44
+ __metadata("design:type", Function),
45
+ __metadata("design:paramtypes", []),
46
+ __metadata("design:returntype", void 0)
47
+ ], JobsController.prototype, "dashboard", null);
48
+ __decorate([
49
+ (0, common_1.Get)('jobs/:name'),
50
+ (0, common_1.Header)('Content-Type', 'text/html; charset=utf-8'),
51
+ __metadata("design:type", Function),
52
+ __metadata("design:paramtypes", []),
53
+ __metadata("design:returntype", void 0)
54
+ ], JobsController.prototype, "dashboardDetail", null);
55
+ __decorate([
56
+ (0, common_1.Get)('api'),
57
+ __metadata("design:type", Function),
58
+ __metadata("design:paramtypes", []),
59
+ __metadata("design:returntype", void 0)
60
+ ], JobsController.prototype, "getJobs", null);
61
+ __decorate([
62
+ (0, common_1.Post)('api/:name/trigger'),
63
+ __param(0, (0, common_1.Param)('name')),
64
+ __metadata("design:type", Function),
65
+ __metadata("design:paramtypes", [String]),
66
+ __metadata("design:returntype", void 0)
67
+ ], JobsController.prototype, "triggerJob", null);
68
+ exports.JobsController = JobsController = __decorate([
69
+ (0, common_1.Controller)(),
70
+ (0, common_1.UseGuards)(basic_auth_guard_1.BasicAuthGuard),
71
+ __metadata("design:paramtypes", [jobs_service_1.JobsService])
72
+ ], JobsController);
@@ -0,0 +1,25 @@
1
+ import { SchedulerRegistry } from '@nestjs/schedule';
2
+ import { Storage } from './storage/storage.abstract';
3
+ export declare class JobsService {
4
+ private readonly schedulerRegistry;
5
+ private readonly storage;
6
+ constructor(schedulerRegistry: SchedulerRegistry, storage: Storage);
7
+ getJobs(): {
8
+ cron: {
9
+ name: string;
10
+ cronExpression: any;
11
+ running: boolean;
12
+ nextRun: string;
13
+ history: import(".").JobExecution[];
14
+ metrics: import(".").JobMetrics;
15
+ }[];
16
+ intervals: {
17
+ name: string;
18
+ }[];
19
+ timeouts: {
20
+ name: string;
21
+ }[];
22
+ };
23
+ triggerJob(name: string): boolean;
24
+ stopExecution(executionId: string): boolean;
25
+ }