@mtakla/cronops 0.1.1-rc6 → 0.1.1-rc7
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 +9 -8
- package/config/jobs/example-job.yaml +1 -1
- package/dist/api/openapi.d.ts +217 -41
- package/dist/api/openapi.js +162 -55
- package/dist/api/webapi.js +28 -2
- package/dist/handlers/AbstractHandler.js +3 -3
- package/dist/server.js +12 -5
- package/dist/tasks/AbstractTask.d.ts +11 -2
- package/dist/tasks/AbstractTask.js +34 -7
- package/dist/tasks/JobRunner.d.ts +2 -3
- package/dist/tasks/JobRunner.js +2 -5
- package/dist/tasks/JobScheduler.d.ts +13 -2
- package/dist/tasks/JobScheduler.js +42 -5
- package/dist/types/Config.types.d.ts +8 -1
- package/dist/types/Config.types.js +1 -1
- package/dist/types/Task.types.d.ts +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ CronOps is built and optimized to run as a Docker container itself.
|
|
|
47
47
|
|
|
48
48
|
```sh
|
|
49
49
|
docker run \
|
|
50
|
+
-p 8083:8083 \
|
|
50
51
|
-v ./config:/config \
|
|
51
52
|
-v ./data:/io/source \
|
|
52
53
|
-v ./data:/io/target \
|
|
@@ -118,12 +119,12 @@ docker compose pull && docker compose up -d
|
|
|
118
119
|
|
|
119
120
|
in the same directory where `compose.yaml` has been created.
|
|
120
121
|
|
|
121
|
-
###
|
|
122
|
+
### Admin Web-API
|
|
122
123
|
|
|
123
124
|
To enable **admin Web-API**, just set `CROPS_API_KEY` environment variable. Details, see [Configuration](#configuration) section below.
|
|
124
125
|
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
## Manual installation
|
|
127
128
|
|
|
128
129
|
This requires [Node.js](https://nodejs.org/) (>= v24) to be installed on your server.
|
|
129
130
|
|
|
@@ -146,15 +147,15 @@ This will ...
|
|
|
146
147
|
|
|
147
148
|
- download the latest version of dotenvx & cronops
|
|
148
149
|
- load the environment settings defined in the `.env` file
|
|
149
|
-
- start the
|
|
150
|
+
- start the CronOps service with the loaded environment settings
|
|
150
151
|
- create config directory in `./config` if it doesn't exist
|
|
151
152
|
- create logs directory in `./logs` if it doesn't exist
|
|
152
153
|
|
|
153
154
|
You can now add job configuration files to `./config/jobs` directory. Each YAML file in this directory defines a job. The server will hot reload when job files are added, modified, or removed.
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
## Use in your code
|
|
156
157
|
|
|
157
|
-
Install CronOps in your
|
|
158
|
+
Install CronOps in your project using npm
|
|
158
159
|
|
|
159
160
|
```
|
|
160
161
|
npm install @mtakla/cronops --save
|
|
@@ -193,8 +194,8 @@ runner.onFinished(() => {
|
|
|
193
194
|
console.log("job finished!");
|
|
194
195
|
});
|
|
195
196
|
|
|
196
|
-
runner.onError(() => {
|
|
197
|
-
console.log(
|
|
197
|
+
runner.onError((err) => {
|
|
198
|
+
console.log(`job failed with ${err.message}`);
|
|
198
199
|
});
|
|
199
200
|
|
|
200
201
|
// finally schedule job
|
|
@@ -362,7 +363,7 @@ For jobs of action type `exec` the following environment variables are available
|
|
|
362
363
|
| `CROPS_TEMP_DIR` | absolute path to the configured temp directory |
|
|
363
364
|
| `CROPS_LOG_DIR` | absolute path to the configured log directory |
|
|
364
365
|
| `CROPS_DRY_RUN` | "true", if dry_run mode is enabled |
|
|
365
|
-
| `CROPS_VERBOSE` | "true", if
|
|
366
|
+
| `CROPS_VERBOSE` | "true", if verbose mode is enabled |
|
|
366
367
|
|
|
367
368
|
|
|
368
369
|
If the exec action is configured to run on selected `source` files:
|
package/dist/api/openapi.d.ts
CHANGED
|
@@ -12,19 +12,144 @@ export declare const openapi: {
|
|
|
12
12
|
bearerFormat: string;
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
|
+
parameters: {
|
|
16
|
+
JobId: {
|
|
17
|
+
name: string;
|
|
18
|
+
in: string;
|
|
19
|
+
required: boolean;
|
|
20
|
+
schema: {
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
schemas: {
|
|
26
|
+
Job: {
|
|
27
|
+
type: string;
|
|
28
|
+
additionalProperties: boolean;
|
|
29
|
+
properties: {
|
|
30
|
+
id: {
|
|
31
|
+
type: string;
|
|
32
|
+
example: string;
|
|
33
|
+
};
|
|
34
|
+
cron: {
|
|
35
|
+
type: string;
|
|
36
|
+
minLength: number;
|
|
37
|
+
example: string;
|
|
38
|
+
};
|
|
39
|
+
action: {
|
|
40
|
+
type: string;
|
|
41
|
+
enum: string[];
|
|
42
|
+
example: string;
|
|
43
|
+
};
|
|
44
|
+
command: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
shell: {
|
|
48
|
+
anyOf: ({
|
|
49
|
+
type: string;
|
|
50
|
+
minLength?: never;
|
|
51
|
+
} | {
|
|
52
|
+
type: string;
|
|
53
|
+
minLength: number;
|
|
54
|
+
})[];
|
|
55
|
+
};
|
|
56
|
+
args: {
|
|
57
|
+
type: string;
|
|
58
|
+
items: {
|
|
59
|
+
type: string;
|
|
60
|
+
minLength: number;
|
|
61
|
+
};
|
|
62
|
+
minItems: number;
|
|
63
|
+
};
|
|
64
|
+
env: {
|
|
65
|
+
type: string;
|
|
66
|
+
propertyNames: {
|
|
67
|
+
type: string;
|
|
68
|
+
pattern: string;
|
|
69
|
+
};
|
|
70
|
+
additionalProperties: {
|
|
71
|
+
type: string;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
source: {
|
|
75
|
+
type: string;
|
|
76
|
+
additionalProperties: boolean;
|
|
77
|
+
properties: {
|
|
78
|
+
dir: {
|
|
79
|
+
type: string;
|
|
80
|
+
minLength: number;
|
|
81
|
+
};
|
|
82
|
+
includes: {
|
|
83
|
+
type: string;
|
|
84
|
+
items: {
|
|
85
|
+
type: string;
|
|
86
|
+
minLength: number;
|
|
87
|
+
};
|
|
88
|
+
minItems: number;
|
|
89
|
+
};
|
|
90
|
+
excludes: {
|
|
91
|
+
type: string;
|
|
92
|
+
items: {
|
|
93
|
+
type: string;
|
|
94
|
+
minLength: number;
|
|
95
|
+
};
|
|
96
|
+
minItems: number;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
target: {
|
|
101
|
+
type: string;
|
|
102
|
+
additionalProperties: boolean;
|
|
103
|
+
properties: {
|
|
104
|
+
dir: {
|
|
105
|
+
type: string;
|
|
106
|
+
minLength: number;
|
|
107
|
+
};
|
|
108
|
+
archive_name: {
|
|
109
|
+
type: string;
|
|
110
|
+
minLength: number;
|
|
111
|
+
};
|
|
112
|
+
permissions: {
|
|
113
|
+
type: string;
|
|
114
|
+
additionalProperties: boolean;
|
|
115
|
+
properties: {
|
|
116
|
+
owner: {
|
|
117
|
+
type: string;
|
|
118
|
+
minLength: number;
|
|
119
|
+
};
|
|
120
|
+
file_mode: {
|
|
121
|
+
type: string;
|
|
122
|
+
minLength: number;
|
|
123
|
+
};
|
|
124
|
+
dir_mode: {
|
|
125
|
+
type: string;
|
|
126
|
+
minLength: number;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
retention: {
|
|
131
|
+
type: string;
|
|
132
|
+
minLength: number;
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
dry_run: {
|
|
137
|
+
type: string;
|
|
138
|
+
};
|
|
139
|
+
verbose: {
|
|
140
|
+
type: string;
|
|
141
|
+
};
|
|
142
|
+
enabled: {
|
|
143
|
+
type: string;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
};
|
|
15
148
|
};
|
|
16
|
-
tags:
|
|
149
|
+
tags: {
|
|
17
150
|
name: string;
|
|
18
151
|
description: string;
|
|
19
|
-
|
|
20
|
-
} | {
|
|
21
|
-
name: string;
|
|
22
|
-
description: string;
|
|
23
|
-
externalDocs: {
|
|
24
|
-
description: string;
|
|
25
|
-
url: string;
|
|
26
|
-
};
|
|
27
|
-
})[];
|
|
152
|
+
}[];
|
|
28
153
|
paths: {
|
|
29
154
|
"/health": {
|
|
30
155
|
get: {
|
|
@@ -83,17 +208,78 @@ export declare const openapi: {
|
|
|
83
208
|
};
|
|
84
209
|
};
|
|
85
210
|
};
|
|
86
|
-
"/api/jobs
|
|
87
|
-
|
|
211
|
+
"/api/jobs": {
|
|
212
|
+
get: {
|
|
213
|
+
summary: string;
|
|
214
|
+
tags: string[];
|
|
215
|
+
responses: {
|
|
216
|
+
"200": {
|
|
217
|
+
description: string;
|
|
218
|
+
content: {
|
|
219
|
+
"application/json": {
|
|
220
|
+
schema: {
|
|
221
|
+
type: string;
|
|
222
|
+
items: {
|
|
223
|
+
$ref: string;
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
"/api/jobs/{jobId}": {
|
|
233
|
+
get: {
|
|
88
234
|
summary: string;
|
|
89
235
|
tags: string[];
|
|
90
236
|
parameters: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
237
|
+
$ref: string;
|
|
238
|
+
}[];
|
|
239
|
+
responses: {
|
|
240
|
+
"200": {
|
|
241
|
+
description: string;
|
|
242
|
+
content: {
|
|
243
|
+
"application/json": {
|
|
244
|
+
schema: {
|
|
245
|
+
$ref: string;
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
"404": {
|
|
251
|
+
description: string;
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
"/api/status": {
|
|
257
|
+
get: {
|
|
258
|
+
summary: string;
|
|
259
|
+
tags: string[];
|
|
260
|
+
responses: {
|
|
261
|
+
"200": {
|
|
262
|
+
description: string;
|
|
263
|
+
content: {
|
|
264
|
+
"application/json": {
|
|
265
|
+
schema: {
|
|
266
|
+
type: string;
|
|
267
|
+
items: {
|
|
268
|
+
$ref: string;
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
};
|
|
96
273
|
};
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
"/api/trigger/{jobId}": {
|
|
278
|
+
post: {
|
|
279
|
+
summary: string;
|
|
280
|
+
tags: string[];
|
|
281
|
+
parameters: {
|
|
282
|
+
$ref: string;
|
|
97
283
|
}[];
|
|
98
284
|
responses: {
|
|
99
285
|
"200": {
|
|
@@ -123,18 +309,10 @@ export declare const openapi: {
|
|
|
123
309
|
};
|
|
124
310
|
};
|
|
125
311
|
};
|
|
126
|
-
"/api/
|
|
312
|
+
"/api/pause": {
|
|
127
313
|
post: {
|
|
128
314
|
summary: string;
|
|
129
315
|
tags: string[];
|
|
130
|
-
parameters: {
|
|
131
|
-
name: string;
|
|
132
|
-
in: string;
|
|
133
|
-
required: boolean;
|
|
134
|
-
schema: {
|
|
135
|
-
type: string;
|
|
136
|
-
};
|
|
137
|
-
}[];
|
|
138
316
|
responses: {
|
|
139
317
|
"200": {
|
|
140
318
|
description: string;
|
|
@@ -147,9 +325,9 @@ export declare const openapi: {
|
|
|
147
325
|
type: string;
|
|
148
326
|
example: boolean;
|
|
149
327
|
};
|
|
150
|
-
|
|
328
|
+
jobs: {
|
|
151
329
|
type: string;
|
|
152
|
-
example:
|
|
330
|
+
example: number;
|
|
153
331
|
};
|
|
154
332
|
};
|
|
155
333
|
};
|
|
@@ -159,17 +337,12 @@ export declare const openapi: {
|
|
|
159
337
|
};
|
|
160
338
|
};
|
|
161
339
|
};
|
|
162
|
-
"/api/
|
|
340
|
+
"/api/pause/job/{jobId}": {
|
|
163
341
|
post: {
|
|
164
342
|
summary: string;
|
|
165
343
|
tags: string[];
|
|
166
344
|
parameters: {
|
|
167
|
-
|
|
168
|
-
in: string;
|
|
169
|
-
required: boolean;
|
|
170
|
-
schema: {
|
|
171
|
-
type: string;
|
|
172
|
-
};
|
|
345
|
+
$ref: string;
|
|
173
346
|
}[];
|
|
174
347
|
responses: {
|
|
175
348
|
"200": {
|
|
@@ -179,7 +352,7 @@ export declare const openapi: {
|
|
|
179
352
|
schema: {
|
|
180
353
|
type: string;
|
|
181
354
|
properties: {
|
|
182
|
-
|
|
355
|
+
paused: {
|
|
183
356
|
type: string;
|
|
184
357
|
example: boolean;
|
|
185
358
|
};
|
|
@@ -195,10 +368,13 @@ export declare const openapi: {
|
|
|
195
368
|
};
|
|
196
369
|
};
|
|
197
370
|
};
|
|
198
|
-
"/api/
|
|
371
|
+
"/api/resume/job/{jobId}": {
|
|
199
372
|
post: {
|
|
200
373
|
summary: string;
|
|
201
374
|
tags: string[];
|
|
375
|
+
parameters: {
|
|
376
|
+
$ref: string;
|
|
377
|
+
}[];
|
|
202
378
|
responses: {
|
|
203
379
|
"200": {
|
|
204
380
|
description: string;
|
|
@@ -207,13 +383,13 @@ export declare const openapi: {
|
|
|
207
383
|
schema: {
|
|
208
384
|
type: string;
|
|
209
385
|
properties: {
|
|
210
|
-
|
|
386
|
+
resumed: {
|
|
211
387
|
type: string;
|
|
212
388
|
example: boolean;
|
|
213
389
|
};
|
|
214
|
-
|
|
390
|
+
jobId: {
|
|
215
391
|
type: string;
|
|
216
|
-
example:
|
|
392
|
+
example: string;
|
|
217
393
|
};
|
|
218
394
|
};
|
|
219
395
|
};
|
|
@@ -223,7 +399,7 @@ export declare const openapi: {
|
|
|
223
399
|
};
|
|
224
400
|
};
|
|
225
401
|
};
|
|
226
|
-
"/api/
|
|
402
|
+
"/api/resume": {
|
|
227
403
|
post: {
|
|
228
404
|
summary: string;
|
|
229
405
|
tags: string[];
|
package/dist/api/openapi.js
CHANGED
|
@@ -9,6 +9,84 @@ export const openapi = {
|
|
|
9
9
|
bearerFormat: "hex-256",
|
|
10
10
|
},
|
|
11
11
|
},
|
|
12
|
+
parameters: {
|
|
13
|
+
JobId: {
|
|
14
|
+
name: "jobId",
|
|
15
|
+
in: "path",
|
|
16
|
+
required: true,
|
|
17
|
+
schema: { type: "string" },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
schemas: {
|
|
21
|
+
Job: {
|
|
22
|
+
type: "object",
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
properties: {
|
|
25
|
+
id: { type: "string", example: "example-job" },
|
|
26
|
+
cron: { type: "string", minLength: 1, example: "*/5 * * * *" },
|
|
27
|
+
action: {
|
|
28
|
+
type: "string",
|
|
29
|
+
enum: ["exec", "call", "copy", "move", "delete", "archive"],
|
|
30
|
+
example: "copy",
|
|
31
|
+
},
|
|
32
|
+
command: { type: "string" },
|
|
33
|
+
shell: {
|
|
34
|
+
anyOf: [{ type: "boolean" }, { type: "string", minLength: 1 }],
|
|
35
|
+
},
|
|
36
|
+
args: {
|
|
37
|
+
type: "array",
|
|
38
|
+
items: { type: "string", minLength: 1 },
|
|
39
|
+
minItems: 1,
|
|
40
|
+
},
|
|
41
|
+
env: {
|
|
42
|
+
type: "object",
|
|
43
|
+
propertyNames: {
|
|
44
|
+
type: "string",
|
|
45
|
+
pattern: "^[A-Z_][A-Z0-9_]*$",
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: { type: "string" },
|
|
48
|
+
},
|
|
49
|
+
source: {
|
|
50
|
+
type: "object",
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
dir: { type: "string", minLength: 1 },
|
|
54
|
+
includes: {
|
|
55
|
+
type: "array",
|
|
56
|
+
items: { type: "string", minLength: 1 },
|
|
57
|
+
minItems: 1,
|
|
58
|
+
},
|
|
59
|
+
excludes: {
|
|
60
|
+
type: "array",
|
|
61
|
+
items: { type: "string", minLength: 1 },
|
|
62
|
+
minItems: 1,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
target: {
|
|
67
|
+
type: "object",
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
properties: {
|
|
70
|
+
dir: { type: "string", minLength: 1 },
|
|
71
|
+
archive_name: { type: "string", minLength: 1 },
|
|
72
|
+
permissions: {
|
|
73
|
+
type: "object",
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
properties: {
|
|
76
|
+
owner: { type: "string", minLength: 1 },
|
|
77
|
+
file_mode: { type: "string", minLength: 1 },
|
|
78
|
+
dir_mode: { type: "string", minLength: 1 },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
retention: { type: "string", minLength: 1 },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
dry_run: { type: "boolean" },
|
|
85
|
+
verbose: { type: "boolean" },
|
|
86
|
+
enabled: { type: "boolean" },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
12
90
|
},
|
|
13
91
|
tags: [
|
|
14
92
|
{
|
|
@@ -17,19 +95,11 @@ export const openapi = {
|
|
|
17
95
|
},
|
|
18
96
|
{
|
|
19
97
|
name: "jobs",
|
|
20
|
-
description: "
|
|
98
|
+
description: "job related api",
|
|
21
99
|
},
|
|
22
100
|
{
|
|
23
|
-
name: "
|
|
24
|
-
description: "
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: "user",
|
|
28
|
-
description: "Operations about user",
|
|
29
|
-
externalDocs: {
|
|
30
|
-
description: "Find out more about our store",
|
|
31
|
-
url: "http://swagger.io",
|
|
32
|
-
},
|
|
101
|
+
name: "schedule",
|
|
102
|
+
description: "scheduling api",
|
|
33
103
|
},
|
|
34
104
|
],
|
|
35
105
|
paths: {
|
|
@@ -84,18 +154,69 @@ export const openapi = {
|
|
|
84
154
|
},
|
|
85
155
|
},
|
|
86
156
|
},
|
|
87
|
-
"/api/jobs
|
|
157
|
+
"/api/jobs": {
|
|
158
|
+
get: {
|
|
159
|
+
summary: "Get jobs",
|
|
160
|
+
tags: ["jobs"],
|
|
161
|
+
responses: {
|
|
162
|
+
"200": {
|
|
163
|
+
description: "Array of scheduled jobs",
|
|
164
|
+
content: {
|
|
165
|
+
"application/json": {
|
|
166
|
+
schema: {
|
|
167
|
+
type: "array",
|
|
168
|
+
items: { $ref: "#/components/schemas/Job" },
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
"/api/jobs/{jobId}": {
|
|
177
|
+
get: {
|
|
178
|
+
summary: "Get a job",
|
|
179
|
+
tags: ["jobs"],
|
|
180
|
+
parameters: [{ $ref: "#/components/parameters/JobId" }],
|
|
181
|
+
responses: {
|
|
182
|
+
"200": {
|
|
183
|
+
description: "Array of scheduled jobs",
|
|
184
|
+
content: {
|
|
185
|
+
"application/json": {
|
|
186
|
+
schema: { $ref: "#/components/schemas/Job" },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
"404": {
|
|
191
|
+
description: "Job not found",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
"/api/status": {
|
|
197
|
+
get: {
|
|
198
|
+
summary: "Get status",
|
|
199
|
+
tags: ["schedule"],
|
|
200
|
+
responses: {
|
|
201
|
+
"200": {
|
|
202
|
+
description: "Array of scheduled jobs",
|
|
203
|
+
content: {
|
|
204
|
+
"application/json": {
|
|
205
|
+
schema: {
|
|
206
|
+
type: "array",
|
|
207
|
+
items: { $ref: "#/components/schemas/Job" },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
"/api/trigger/{jobId}": {
|
|
88
216
|
post: {
|
|
89
217
|
summary: "Trigger a job",
|
|
90
|
-
tags: ["
|
|
91
|
-
parameters: [
|
|
92
|
-
{
|
|
93
|
-
name: "jobId",
|
|
94
|
-
in: "path",
|
|
95
|
-
required: true,
|
|
96
|
-
schema: { type: "string" },
|
|
97
|
-
},
|
|
98
|
-
],
|
|
218
|
+
tags: ["schedule"],
|
|
219
|
+
parameters: [{ $ref: "#/components/parameters/JobId" }],
|
|
99
220
|
responses: {
|
|
100
221
|
"200": {
|
|
101
222
|
description: "Triggered",
|
|
@@ -118,18 +239,10 @@ export const openapi = {
|
|
|
118
239
|
},
|
|
119
240
|
},
|
|
120
241
|
},
|
|
121
|
-
"/api/
|
|
242
|
+
"/api/pause": {
|
|
122
243
|
post: {
|
|
123
|
-
summary: "Pause
|
|
124
|
-
tags: ["
|
|
125
|
-
parameters: [
|
|
126
|
-
{
|
|
127
|
-
name: "jobId",
|
|
128
|
-
in: "path",
|
|
129
|
-
required: true,
|
|
130
|
-
schema: { type: "string" },
|
|
131
|
-
},
|
|
132
|
-
],
|
|
244
|
+
summary: "Pause jobs",
|
|
245
|
+
tags: ["schedule"],
|
|
133
246
|
responses: {
|
|
134
247
|
"200": {
|
|
135
248
|
description: "Paused",
|
|
@@ -139,7 +252,7 @@ export const openapi = {
|
|
|
139
252
|
type: "object",
|
|
140
253
|
properties: {
|
|
141
254
|
paused: { type: "boolean", example: true },
|
|
142
|
-
|
|
255
|
+
jobs: { type: "number", example: 4 },
|
|
143
256
|
},
|
|
144
257
|
},
|
|
145
258
|
},
|
|
@@ -148,27 +261,20 @@ export const openapi = {
|
|
|
148
261
|
},
|
|
149
262
|
},
|
|
150
263
|
},
|
|
151
|
-
"/api/
|
|
264
|
+
"/api/pause/job/{jobId}": {
|
|
152
265
|
post: {
|
|
153
|
-
summary: "
|
|
154
|
-
tags: ["
|
|
155
|
-
parameters: [
|
|
156
|
-
{
|
|
157
|
-
name: "jobId",
|
|
158
|
-
in: "path",
|
|
159
|
-
required: true,
|
|
160
|
-
schema: { type: "string" },
|
|
161
|
-
},
|
|
162
|
-
],
|
|
266
|
+
summary: "Pause a job",
|
|
267
|
+
tags: ["schedule"],
|
|
268
|
+
parameters: [{ $ref: "#/components/parameters/JobId" }],
|
|
163
269
|
responses: {
|
|
164
270
|
"200": {
|
|
165
|
-
description: "
|
|
271
|
+
description: "Paused",
|
|
166
272
|
content: {
|
|
167
273
|
"application/json": {
|
|
168
274
|
schema: {
|
|
169
275
|
type: "object",
|
|
170
276
|
properties: {
|
|
171
|
-
|
|
277
|
+
paused: { type: "boolean", example: true },
|
|
172
278
|
jobId: { type: "string", example: "job-123" },
|
|
173
279
|
},
|
|
174
280
|
},
|
|
@@ -178,20 +284,21 @@ export const openapi = {
|
|
|
178
284
|
},
|
|
179
285
|
},
|
|
180
286
|
},
|
|
181
|
-
"/api/
|
|
287
|
+
"/api/resume/job/{jobId}": {
|
|
182
288
|
post: {
|
|
183
|
-
summary: "
|
|
184
|
-
tags: ["
|
|
289
|
+
summary: "Resume a paused job",
|
|
290
|
+
tags: ["schedule"],
|
|
291
|
+
parameters: [{ $ref: "#/components/parameters/JobId" }],
|
|
185
292
|
responses: {
|
|
186
293
|
"200": {
|
|
187
|
-
description: "
|
|
294
|
+
description: "Resumed",
|
|
188
295
|
content: {
|
|
189
296
|
"application/json": {
|
|
190
297
|
schema: {
|
|
191
298
|
type: "object",
|
|
192
299
|
properties: {
|
|
193
|
-
|
|
194
|
-
|
|
300
|
+
resumed: { type: "boolean", example: true },
|
|
301
|
+
jobId: { type: "string", example: "job-123" },
|
|
195
302
|
},
|
|
196
303
|
},
|
|
197
304
|
},
|
|
@@ -200,10 +307,10 @@ export const openapi = {
|
|
|
200
307
|
},
|
|
201
308
|
},
|
|
202
309
|
},
|
|
203
|
-
"/api/
|
|
310
|
+
"/api/resume": {
|
|
204
311
|
post: {
|
|
205
|
-
summary: "Resume
|
|
206
|
-
tags: ["
|
|
312
|
+
summary: "Resume paused jobs",
|
|
313
|
+
tags: ["schedule"],
|
|
207
314
|
responses: {
|
|
208
315
|
"200": {
|
|
209
316
|
description: "Resumed",
|
package/dist/api/webapi.js
CHANGED
|
@@ -5,7 +5,7 @@ import { openapi } from "./openapi.js";
|
|
|
5
5
|
const app = Fastify();
|
|
6
6
|
const port = Number(process.env[ENV.PORT] ?? 8118);
|
|
7
7
|
const host = process.env[ENV.HOST] ?? "127.0.0.1";
|
|
8
|
-
const baseUrl = process.env[ENV.BASE_URL] ??
|
|
8
|
+
const baseUrl = process.env[ENV.BASE_URL] ?? `http://127.0.0.1:${port}`;
|
|
9
9
|
const apiKey = process.env[ENV.API_KEY];
|
|
10
10
|
app.addHook("preHandler", async (request, reply) => {
|
|
11
11
|
if (request.method === "OPTIONS" || !request.url.startsWith("/api"))
|
|
@@ -38,7 +38,33 @@ app.get("/health", async (request, reply) => {
|
|
|
38
38
|
const jobs = jobScheduler.getScheduledJobs();
|
|
39
39
|
reply.code(200).send({ status: "ok", active_jobs: jobs.length });
|
|
40
40
|
});
|
|
41
|
-
app.
|
|
41
|
+
app.get("/api/jobs", async (request) => {
|
|
42
|
+
const jobScheduler = request.server.scheduler;
|
|
43
|
+
return jobScheduler.getScheduledJobs();
|
|
44
|
+
});
|
|
45
|
+
app.get("/api/jobs/:jobId", async (request, reply) => {
|
|
46
|
+
const { jobId } = request.params;
|
|
47
|
+
const jobScheduler = request.server.scheduler;
|
|
48
|
+
if (!jobScheduler.isJobScheduled(jobId)) {
|
|
49
|
+
return reply.code(404).send();
|
|
50
|
+
}
|
|
51
|
+
return jobScheduler.getJob(jobId);
|
|
52
|
+
});
|
|
53
|
+
app.get("/api/status", async (request) => {
|
|
54
|
+
const jobScheduler = request.server.scheduler;
|
|
55
|
+
return jobScheduler.getScheduledJobsInfo();
|
|
56
|
+
});
|
|
57
|
+
app.post("/api/pause", async (request) => {
|
|
58
|
+
const jobScheduler = request.server.scheduler;
|
|
59
|
+
await jobScheduler.pauseScheduling();
|
|
60
|
+
return { paused: true };
|
|
61
|
+
});
|
|
62
|
+
app.post("/api/resume", async (request) => {
|
|
63
|
+
const jobScheduler = request.server.scheduler;
|
|
64
|
+
await jobScheduler.resumeScheduling();
|
|
65
|
+
return { resumed: true };
|
|
66
|
+
});
|
|
67
|
+
app.post("/api/trigger/:jobId", async (request, reply) => {
|
|
42
68
|
const { jobId } = request.params;
|
|
43
69
|
const jobScheduler = request.server.scheduler;
|
|
44
70
|
if (!jobScheduler.isJobScheduled(jobId)) {
|
|
@@ -141,12 +141,12 @@ export class AbstractHandler {
|
|
|
141
141
|
await Promise.all(targetScanPromises);
|
|
142
142
|
fileHistory.cleanup();
|
|
143
143
|
}
|
|
144
|
-
async setTargetFilePermissions(
|
|
144
|
+
async setTargetFilePermissions(destFile, perms, isDir = false) {
|
|
145
145
|
const mode = isDir ? perms.dirMode : perms.fileMode;
|
|
146
146
|
if (perms.uid >= 0 && perms.gid >= 0)
|
|
147
|
-
await fsx.chown(
|
|
147
|
+
await fsx.chown(destFile, perms.uid, perms.gid);
|
|
148
148
|
if (mode >= 0)
|
|
149
|
-
await fsx.chmod(
|
|
149
|
+
await fsx.chmod(destFile, mode);
|
|
150
150
|
}
|
|
151
151
|
getFolderPermissionPromises(ctx, dirPath, folderPromises) {
|
|
152
152
|
if (dirPath.length < ctx.targetDir.length || folderPromises.has(dirPath))
|
package/dist/server.js
CHANGED
|
@@ -34,18 +34,25 @@ export async function start() {
|
|
|
34
34
|
console.log(`🔴 Error loading job '${entry}'. ${message}`);
|
|
35
35
|
});
|
|
36
36
|
jobLoader.onJobLoaded((job) => {
|
|
37
|
-
|
|
38
|
-
jobScheduler.scheduleJob(job);
|
|
37
|
+
jobScheduler.scheduleJob(job);
|
|
39
38
|
});
|
|
40
39
|
jobLoader.onJobDeleted((jobId) => {
|
|
41
40
|
jobScheduler.unscheduleJob(jobId);
|
|
42
41
|
});
|
|
43
42
|
jobScheduler.onChanged((isReload) => {
|
|
44
|
-
const jobs = jobScheduler.
|
|
45
|
-
console.log(`\nJob config ${isReload ? "changed" : "loaded"}
|
|
43
|
+
const jobs = jobScheduler.getScheduledJobsInfo();
|
|
44
|
+
console.log(`\nJob config ${isReload ? "changed" : "loaded"}`);
|
|
46
45
|
for (const job of jobs) {
|
|
47
|
-
|
|
46
|
+
if (job.status !== "paused")
|
|
47
|
+
console.log(` 🕔 [${job.id}] scheduled (${chalk.greenBright(job.cron)})${job.dry_run ? " 👋 DRY-RUN mode!" : ""}`);
|
|
48
48
|
}
|
|
49
|
+
for (const job of jobs) {
|
|
50
|
+
if (job.status === "paused")
|
|
51
|
+
console.log(` ⚫ [${job.id}] inactive`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
jobScheduler.onJobExecute((job) => {
|
|
55
|
+
console.error(`[${job.id}] triggered manually`);
|
|
49
56
|
});
|
|
50
57
|
jobScheduler.onJobError((job, err) => {
|
|
51
58
|
console.error(chalk.red(`[${job.id}] ERROR ${err.message}`));
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { type ScheduledTask } from "node-cron";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import type { Task } from "../types/Task.types.js";
|
|
3
|
+
import type { Task, TaskInfo } from "../types/Task.types.js";
|
|
4
4
|
export declare abstract class AbstractTask<T> implements Task {
|
|
5
5
|
protected cronTask: ScheduledTask;
|
|
6
6
|
protected events: EventEmitter<any>;
|
|
7
7
|
protected errorCount: number;
|
|
8
|
+
private runCount;
|
|
9
|
+
private isScheduled;
|
|
8
10
|
private isRunning;
|
|
11
|
+
private isPaused;
|
|
12
|
+
private lastRun?;
|
|
13
|
+
private lastDuration?;
|
|
9
14
|
constructor(cronStr?: string);
|
|
10
15
|
protected abstract run(): Promise<T>;
|
|
11
16
|
schedule(runImmediately?: boolean): void;
|
|
12
17
|
unschedule(): void;
|
|
13
|
-
|
|
18
|
+
pause(): void;
|
|
19
|
+
resume(): void;
|
|
20
|
+
getInfo(): TaskInfo;
|
|
21
|
+
execute<T>(): Promise<T>;
|
|
14
22
|
onScheduled(cb: () => void): void;
|
|
23
|
+
onExecute(cb: () => void): void;
|
|
15
24
|
onStarted(cb: () => void): void;
|
|
16
25
|
onFinished<T>(cb: (result: T) => void): void;
|
|
17
26
|
onError(cb: (error: Error) => void): void;
|
|
@@ -6,20 +6,29 @@ export class AbstractTask {
|
|
|
6
6
|
cronTask;
|
|
7
7
|
events = new EventEmitter();
|
|
8
8
|
errorCount = 0;
|
|
9
|
+
runCount = 0;
|
|
10
|
+
isScheduled = false;
|
|
9
11
|
isRunning = false;
|
|
12
|
+
isPaused = false;
|
|
13
|
+
lastRun;
|
|
14
|
+
lastDuration;
|
|
10
15
|
constructor(cronStr = "* * * * *") {
|
|
11
16
|
const asyncRunner = async () => {
|
|
12
|
-
if (!this.isRunning) {
|
|
17
|
+
if (!this.isRunning && !this.isPaused) {
|
|
18
|
+
this.runCount++;
|
|
13
19
|
this.isRunning = true;
|
|
20
|
+
this.lastRun = Date.now();
|
|
14
21
|
try {
|
|
15
22
|
this.events.emit("started");
|
|
16
23
|
this.events.emit("finished", await this.run());
|
|
17
24
|
}
|
|
18
25
|
catch (err) {
|
|
26
|
+
this.errorCount++;
|
|
19
27
|
this.events.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
20
28
|
}
|
|
21
29
|
finally {
|
|
22
30
|
this.isRunning = false;
|
|
31
|
+
this.lastDuration = Date.now() - this.lastRun;
|
|
23
32
|
}
|
|
24
33
|
}
|
|
25
34
|
};
|
|
@@ -30,26 +39,45 @@ export class AbstractTask {
|
|
|
30
39
|
}
|
|
31
40
|
schedule(runImmediately = false) {
|
|
32
41
|
if (runImmediately)
|
|
33
|
-
this.cronTask.once("task:started", () => this.execute());
|
|
42
|
+
this.cronTask.once("task:started", () => this.cronTask.execute());
|
|
34
43
|
this.cronTask.start();
|
|
44
|
+
this.isScheduled = true;
|
|
35
45
|
}
|
|
36
46
|
unschedule() {
|
|
37
47
|
this.events.removeAllListeners();
|
|
38
48
|
this.cronTask.destroy();
|
|
49
|
+
this.isScheduled = false;
|
|
39
50
|
}
|
|
40
|
-
|
|
51
|
+
pause() {
|
|
52
|
+
this.isPaused = true;
|
|
53
|
+
}
|
|
54
|
+
resume() {
|
|
55
|
+
this.isPaused = false;
|
|
56
|
+
}
|
|
57
|
+
getInfo() {
|
|
58
|
+
return {
|
|
59
|
+
status: this.isRunning ? "running" : this.isPaused ? "paused" : this.isScheduled ? "scheduled" : "unscheduled",
|
|
60
|
+
runCount: this.runCount,
|
|
61
|
+
errorCount: this.errorCount,
|
|
62
|
+
lastRun: this.lastRun,
|
|
63
|
+
lastDuration: this.lastDuration,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
execute() {
|
|
41
67
|
const status = this.cronTask.getStatus();
|
|
42
68
|
if (status === "destroyed")
|
|
43
69
|
throw new Error("Invalid task state (destroyed)");
|
|
44
70
|
if (status === "running" || this.isRunning)
|
|
45
71
|
throw new Error("Invalid task state (running)");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.cronTask.execute();
|
|
72
|
+
this.events.emit("execute");
|
|
73
|
+
return this.cronTask.execute();
|
|
49
74
|
}
|
|
50
75
|
onScheduled(cb) {
|
|
51
76
|
this.cronTask.on("task:started", cb);
|
|
52
77
|
}
|
|
78
|
+
onExecute(cb) {
|
|
79
|
+
this.events.on("execute", cb);
|
|
80
|
+
}
|
|
53
81
|
onStarted(cb) {
|
|
54
82
|
this.events.on("started", cb);
|
|
55
83
|
}
|
|
@@ -57,7 +85,6 @@ export class AbstractTask {
|
|
|
57
85
|
this.events.on("finished", cb);
|
|
58
86
|
}
|
|
59
87
|
onError(cb) {
|
|
60
|
-
this.errorCount++;
|
|
61
88
|
this.events.on("error", (error) => cb(error));
|
|
62
89
|
}
|
|
63
90
|
async gracefulTerminate(timeout = 500) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AbstractTask } from "./AbstractTask.js";
|
|
2
|
-
import { JobRunnerResult } from "../models/JobRunnerResult.js";
|
|
3
2
|
import type { JobModel } from "../models/JobModel.js";
|
|
4
3
|
import type { JobRunnerSetup } from "../models/JobRunnerSetup.js";
|
|
5
4
|
import type { RunnerResult, FileHistory } from "../types/Task.types.js";
|
|
@@ -9,8 +8,8 @@ export declare class JobRunner extends AbstractTask<RunnerResult> {
|
|
|
9
8
|
setup: JobRunnerSetup;
|
|
10
9
|
constructor(job: JobModel, setup: JobRunnerSetup);
|
|
11
10
|
onActivity(cb: (action: string, path: string, count: number) => void): void;
|
|
12
|
-
runJob(): Promise<
|
|
13
|
-
protected run(): Promise<
|
|
11
|
+
runJob(): Promise<RunnerResult>;
|
|
12
|
+
protected run(): Promise<RunnerResult>;
|
|
14
13
|
protected loadFileHistory(job: Job): Promise<FileHistory>;
|
|
15
14
|
protected saveFileHistory(job: Job, fileHistory: FileHistory): Promise<void>;
|
|
16
15
|
protected initLog(job: Job): number;
|
package/dist/tasks/JobRunner.js
CHANGED
|
@@ -4,7 +4,6 @@ import { ensureDir, moveSync, readJSON, writeJSON } from "fs-extra/esm";
|
|
|
4
4
|
import { AbstractTask } from "./AbstractTask.js";
|
|
5
5
|
import { JobRunnerContext } from "../models/JobRunnerContext.js";
|
|
6
6
|
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
7
|
-
import { JobRunnerResult } from "../models/JobRunnerResult.js";
|
|
8
7
|
import { FileHistoryModel } from "../models/FileHistoryModel.js";
|
|
9
8
|
export class JobRunner extends AbstractTask {
|
|
10
9
|
job;
|
|
@@ -22,10 +21,8 @@ export class JobRunner extends AbstractTask {
|
|
|
22
21
|
}
|
|
23
22
|
async run() {
|
|
24
23
|
const { setup, job, events } = this;
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
if (this.errorCount >= 25) {
|
|
28
|
-
job.enabled = false;
|
|
24
|
+
if (this.errorCount >= 31) {
|
|
25
|
+
this.pause();
|
|
29
26
|
throw new Error("Too many errors. Job execution disabled!");
|
|
30
27
|
}
|
|
31
28
|
await ensureDir(setup.logDir);
|
|
@@ -2,8 +2,9 @@ import { JobRunner } from "./JobRunner.js";
|
|
|
2
2
|
import { JobRunnerSetup } from "../models/JobRunnerSetup.js";
|
|
3
3
|
import { AbstractTask } from "./AbstractTask.js";
|
|
4
4
|
import type { Job } from "../types/Config.types.js";
|
|
5
|
-
import type { RunnerResult } from "../types/Task.types.js";
|
|
5
|
+
import type { RunnerResult, TaskInfo } from "../types/Task.types.js";
|
|
6
6
|
import type { RunnerOptions } from "../types/Options.types.js";
|
|
7
|
+
type SchedulingInfo = Job & TaskInfo;
|
|
7
8
|
export declare class JobScheduler extends AbstractTask<void> {
|
|
8
9
|
protected runnerSetup: JobRunnerSetup;
|
|
9
10
|
protected runnerMap: Map<string, JobRunner>;
|
|
@@ -13,19 +14,29 @@ export declare class JobScheduler extends AbstractTask<void> {
|
|
|
13
14
|
get scheduledJobs(): number;
|
|
14
15
|
get tempDir(): string;
|
|
15
16
|
protected run(): Promise<void>;
|
|
17
|
+
pauseAll(): void;
|
|
18
|
+
resumeAll(): void;
|
|
16
19
|
unscheduleAll(): void;
|
|
17
20
|
scheduleJobs(jobs: Job[], cb?: (count: number) => void): void;
|
|
18
|
-
scheduleJob(job: Job
|
|
21
|
+
scheduleJob(job: Job): void;
|
|
19
22
|
unscheduleJob(jobId: string): void;
|
|
20
23
|
isJobScheduled(jobId: string): boolean;
|
|
24
|
+
getJob(jobId: string): Job | undefined;
|
|
21
25
|
executeJob(jobId: string): void;
|
|
22
26
|
validateJob(job: Job): void;
|
|
27
|
+
pauseScheduling(): void;
|
|
28
|
+
resumeScheduling(): void;
|
|
29
|
+
pauseJobScheduling(jobId: string): void;
|
|
30
|
+
resumeJobScheduling(jobId: string): void;
|
|
23
31
|
getScheduledJobs(): Job[];
|
|
32
|
+
getScheduledJobsInfo(): SchedulingInfo[];
|
|
24
33
|
gracefulTerminate(timeout?: number): Promise<void>;
|
|
25
34
|
onChanged(cb: (initialConfig: boolean) => void): void;
|
|
26
35
|
onJobScheduled(cb: (job: Job, rescheduled: boolean) => void): void;
|
|
36
|
+
onJobExecute(cb: (job: Job) => void): void;
|
|
27
37
|
onJobStarted(cb: (job: Job) => void): void;
|
|
28
38
|
onJobFinished(cb: (job: Job, stat: RunnerResult) => void): void;
|
|
29
39
|
onJobActivity(cb: (job: Job, activity: string, path: string, count: number) => void): void;
|
|
30
40
|
onJobError(cb: (job: Job, err: Error) => void): void;
|
|
31
41
|
}
|
|
42
|
+
export {};
|
|
@@ -2,7 +2,6 @@ import { JobRunner } from "./JobRunner.js";
|
|
|
2
2
|
import { JobModel } from "../models/JobModel.js";
|
|
3
3
|
import { JobRunnerSetup } from "../models/JobRunnerSetup.js";
|
|
4
4
|
import { AbstractTask } from "./AbstractTask.js";
|
|
5
|
-
import { JobError } from "../errors/JobError.js";
|
|
6
5
|
export class JobScheduler extends AbstractTask {
|
|
7
6
|
runnerSetup;
|
|
8
7
|
runnerMap;
|
|
@@ -26,6 +25,14 @@ export class JobScheduler extends AbstractTask {
|
|
|
26
25
|
this.isReload = true;
|
|
27
26
|
}
|
|
28
27
|
}
|
|
28
|
+
pauseAll() {
|
|
29
|
+
for (const runner of this.runnerMap.values())
|
|
30
|
+
runner.resume();
|
|
31
|
+
}
|
|
32
|
+
resumeAll() {
|
|
33
|
+
for (const runner of this.runnerMap.values())
|
|
34
|
+
runner.resume();
|
|
35
|
+
}
|
|
29
36
|
unscheduleAll() {
|
|
30
37
|
for (const runner of this.runnerMap.values())
|
|
31
38
|
runner.unschedule();
|
|
@@ -39,23 +46,24 @@ export class JobScheduler extends AbstractTask {
|
|
|
39
46
|
if (cb)
|
|
40
47
|
cb(this.runnerMap.size);
|
|
41
48
|
}
|
|
42
|
-
scheduleJob(job
|
|
49
|
+
scheduleJob(job) {
|
|
43
50
|
let rescheduled = false;
|
|
44
|
-
if (job.enabled === false)
|
|
45
|
-
JobError.throw(job.id, `Cannot schedule disabled job [${job.id}]!`);
|
|
46
51
|
this.runnerSetup.validateJob(job);
|
|
47
52
|
if (this.runnerMap.has(job.id)) {
|
|
48
53
|
this.runnerMap.get(job.id)?.unschedule();
|
|
49
54
|
rescheduled = true;
|
|
50
55
|
}
|
|
51
|
-
const task = new JobRunner(new JobModel(job
|
|
56
|
+
const task = new JobRunner(new JobModel(job), this.runnerSetup);
|
|
52
57
|
this.runnerMap.set(job.id, task);
|
|
53
58
|
task.onScheduled(() => this.events.emit("job-scheduled", job, rescheduled));
|
|
59
|
+
task.onExecute(() => this.events.emit("job-execute", job));
|
|
54
60
|
task.onStarted(() => this.events.emit("job-started", job));
|
|
55
61
|
task.onFinished((stat) => this.events.emit("job-finished", job, stat));
|
|
56
62
|
task.onActivity((activity, path, count) => this.events.emit("job-activity", job, activity, path, count));
|
|
57
63
|
task.onError((err) => this.events.emit("job-error", job, err));
|
|
58
64
|
task.schedule();
|
|
65
|
+
if (job.enabled === false)
|
|
66
|
+
task.pause();
|
|
59
67
|
this.changed = true;
|
|
60
68
|
}
|
|
61
69
|
unscheduleJob(jobId) {
|
|
@@ -69,6 +77,9 @@ export class JobScheduler extends AbstractTask {
|
|
|
69
77
|
isJobScheduled(jobId) {
|
|
70
78
|
return this.runnerMap.has(jobId);
|
|
71
79
|
}
|
|
80
|
+
getJob(jobId) {
|
|
81
|
+
return this.runnerMap.get(jobId)?.job;
|
|
82
|
+
}
|
|
72
83
|
executeJob(jobId) {
|
|
73
84
|
if (!this.runnerMap.has(jobId))
|
|
74
85
|
throw new Error(`Unknown job [${jobId}]!`);
|
|
@@ -77,9 +88,32 @@ export class JobScheduler extends AbstractTask {
|
|
|
77
88
|
validateJob(job) {
|
|
78
89
|
this.runnerSetup.validateJob(job);
|
|
79
90
|
}
|
|
91
|
+
pauseScheduling() {
|
|
92
|
+
for (const task of this.runnerMap.values())
|
|
93
|
+
this.pauseJobScheduling(task.job.id);
|
|
94
|
+
this.changed = true;
|
|
95
|
+
}
|
|
96
|
+
resumeScheduling() {
|
|
97
|
+
for (const task of this.runnerMap.values())
|
|
98
|
+
this.resumeJobScheduling(task.job.id);
|
|
99
|
+
this.changed = true;
|
|
100
|
+
}
|
|
101
|
+
pauseJobScheduling(jobId) {
|
|
102
|
+
const task = this.runnerMap.get(jobId);
|
|
103
|
+
if (task)
|
|
104
|
+
task.pause();
|
|
105
|
+
}
|
|
106
|
+
resumeJobScheduling(jobId) {
|
|
107
|
+
const task = this.runnerMap.get(jobId);
|
|
108
|
+
if (task && task.job.enabled !== false)
|
|
109
|
+
task.resume();
|
|
110
|
+
}
|
|
80
111
|
getScheduledJobs() {
|
|
81
112
|
return Array.from(this.runnerMap.values()).map((runner) => runner.job);
|
|
82
113
|
}
|
|
114
|
+
getScheduledJobsInfo() {
|
|
115
|
+
return Array.from(this.runnerMap.values()).map((runner) => Object.assign({}, runner.job, runner.getInfo()));
|
|
116
|
+
}
|
|
83
117
|
async gracefulTerminate(timeout = 1000) {
|
|
84
118
|
const jobRunnerTasks = [...this.runnerMap.values()];
|
|
85
119
|
this.unscheduleAll();
|
|
@@ -93,6 +127,9 @@ export class JobScheduler extends AbstractTask {
|
|
|
93
127
|
onJobScheduled(cb) {
|
|
94
128
|
this.events.on("job-scheduled", cb);
|
|
95
129
|
}
|
|
130
|
+
onJobExecute(cb) {
|
|
131
|
+
this.events.on("job-execute", cb);
|
|
132
|
+
}
|
|
96
133
|
onJobStarted(cb) {
|
|
97
134
|
this.events.on("job-started", cb);
|
|
98
135
|
}
|
|
@@ -2,7 +2,14 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const JobSchema: z.ZodObject<{
|
|
3
3
|
id: z.ZodOptional<z.ZodString>;
|
|
4
4
|
cron: z.ZodOptional<z.ZodString>;
|
|
5
|
-
action: z.
|
|
5
|
+
action: z.ZodEnum<{
|
|
6
|
+
exec: "exec";
|
|
7
|
+
call: "call";
|
|
8
|
+
copy: "copy";
|
|
9
|
+
move: "move";
|
|
10
|
+
delete: "delete";
|
|
11
|
+
archive: "archive";
|
|
12
|
+
}>;
|
|
6
13
|
command: z.ZodOptional<z.ZodString>;
|
|
7
14
|
shell: z.ZodOptional<z.ZodUnion<[z.ZodBoolean, z.ZodString]>>;
|
|
8
15
|
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
export const JobSchema = z.strictObject({
|
|
3
3
|
id: z.string().optional(),
|
|
4
4
|
cron: z.string().min(1).optional(),
|
|
5
|
-
action: z.
|
|
5
|
+
action: z.enum(["exec", "call", "copy", "move", "delete", "archive"]),
|
|
6
6
|
command: z.string().optional(),
|
|
7
7
|
shell: z.boolean().or(z.string().min(1)).optional(),
|
|
8
8
|
args: z.array(z.string().min(1)).min(1).optional(),
|
|
@@ -13,6 +13,13 @@ export type RunnerResult = {
|
|
|
13
13
|
endTime: number;
|
|
14
14
|
durationMs: number;
|
|
15
15
|
};
|
|
16
|
+
export type TaskInfo = {
|
|
17
|
+
status: "unscheduled" | "scheduled" | "running" | "paused";
|
|
18
|
+
runCount: number;
|
|
19
|
+
errorCount: number;
|
|
20
|
+
lastRun: number | undefined;
|
|
21
|
+
lastDuration: number | undefined;
|
|
22
|
+
};
|
|
16
23
|
export type SourceFile = {
|
|
17
24
|
sourceEntry: string;
|
|
18
25
|
sourcePath: string;
|
|
@@ -36,7 +43,7 @@ export type FileHistory = {
|
|
|
36
43
|
export interface Task {
|
|
37
44
|
schedule(runImmediately?: boolean): void;
|
|
38
45
|
unschedule(): void;
|
|
39
|
-
execute(
|
|
46
|
+
execute<T>(): Promise<T>;
|
|
40
47
|
onScheduled(cb: () => void): void;
|
|
41
48
|
onStarted(cb: () => void): void;
|
|
42
49
|
onFinished<T>(cb: (result: T) => void): void;
|