agendash 2.1.1 → 3.0.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/History.md +10 -0
- package/README.md +27 -0
- package/bin/agendash-standalone-fastify.js +61 -0
- package/bin/agendash-standalone-hapi.js +1 -1
- package/bin/agendash-standalone-koa.js +1 -1
- package/bin/agendash-standalone.js +1 -1
- package/lib/controllers/agendash.js +5 -8
- package/lib/middlewares/README.md +1 -1
- package/lib/middlewares/fastify.js +75 -0
- package/package.json +7 -5
- package/public/app/js/jobdetail.js +1 -1
- package/public/app/js/joblist.js +14 -10
package/History.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# 3.0.0
|
|
2
|
+
|
|
3
|
+
- BREAKING: Drop support for MongoDB v3.4, only >=v3.6 is supported now.
|
|
4
|
+
- BREAKING: Drop support for node 10, but added node 16 support.
|
|
5
|
+
- feat: add `fastify` support #194
|
|
6
|
+
- fix: a lot of rendering issues
|
|
7
|
+
- fix: newer version of `mongodb` compatibility #193
|
|
8
|
+
|
|
9
|
+
This file won't mention contributors anymore. Because all contributors are listed on the [main page](https://github.com/agenda/agendash) now. Thanks, GitHub!
|
|
10
|
+
|
|
1
11
|
# 2.1.0 - JSON validation when creating jobs
|
|
2
12
|
|
|
3
13
|
- feat: Add json validator message when createJob #169 (thanks @love8587)
|
package/README.md
CHANGED
|
@@ -191,6 +191,33 @@ await app.listen(3002);
|
|
|
191
191
|
|
|
192
192
|
Then browse to `http://localhost:3002/`.
|
|
193
193
|
|
|
194
|
+
#### Fastify
|
|
195
|
+
|
|
196
|
+
```shell
|
|
197
|
+
npm i fastify
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
const agenda = new Agenda().database(
|
|
202
|
+
"mongodb://127.0.0.1/agendaDb",
|
|
203
|
+
"agendaJobs"
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const Fastify = require("fastify");
|
|
207
|
+
const fastify = new Fastify();
|
|
208
|
+
|
|
209
|
+
fastify.register(
|
|
210
|
+
Agendash(
|
|
211
|
+
agenda,
|
|
212
|
+
{ middleware: "fastify" }
|
|
213
|
+
);
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
await fastify.listen(3002);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Then browse to `http://localhost:3002/`.
|
|
220
|
+
|
|
194
221
|
### Standalone usage
|
|
195
222
|
|
|
196
223
|
Agendash comes with a standalone Express app which you can use like this:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const Agenda = require("agenda");
|
|
4
|
+
const agendash = require("../app");
|
|
5
|
+
const Fastify = require("fastify");
|
|
6
|
+
const program = require("commander");
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.option(
|
|
10
|
+
"-d, --db <db>",
|
|
11
|
+
"[required] Mongo connection string, same as Agenda connection string"
|
|
12
|
+
)
|
|
13
|
+
.option(
|
|
14
|
+
"-c, --collection <collection>",
|
|
15
|
+
"[optional] Mongo collection, same as Agenda collection name, default agendaJobs",
|
|
16
|
+
"agendaJobs"
|
|
17
|
+
)
|
|
18
|
+
.option(
|
|
19
|
+
"-p, --port <port>",
|
|
20
|
+
"[optional] Server port, default 3000",
|
|
21
|
+
(n, d) => Number(n) || d,
|
|
22
|
+
3000
|
|
23
|
+
)
|
|
24
|
+
.option(
|
|
25
|
+
"-t, --title <title>",
|
|
26
|
+
"[optional] Page title, default Agendash",
|
|
27
|
+
"Agendash"
|
|
28
|
+
)
|
|
29
|
+
.option(
|
|
30
|
+
"-p, --path <path>",
|
|
31
|
+
"[optional] Path to bind Agendash to, default /",
|
|
32
|
+
"/"
|
|
33
|
+
)
|
|
34
|
+
.parse(process.argv);
|
|
35
|
+
|
|
36
|
+
if (!program.db) {
|
|
37
|
+
console.error("--db required");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!program.path.startsWith("/")) {
|
|
42
|
+
console.error("--path must begin with /");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const fastify = Fastify();
|
|
47
|
+
|
|
48
|
+
const agenda = new Agenda().database(program.db, program.collection);
|
|
49
|
+
|
|
50
|
+
fastify.register(
|
|
51
|
+
agendash(agenda, {
|
|
52
|
+
middleware: "fastify",
|
|
53
|
+
title: program.title,
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
fastify.listen(program.port, function () {
|
|
58
|
+
console.log(
|
|
59
|
+
`Agendash started http://localhost:${program.port}${program.path}`
|
|
60
|
+
);
|
|
61
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const semver = require("semver");
|
|
3
|
-
const {
|
|
3
|
+
const { ObjectId } = require("mongodb"); // We rely on the Agenda's "mongodb", thus our package.json lists "*" as required version
|
|
4
4
|
|
|
5
5
|
module.exports = function (agenda, options) {
|
|
6
6
|
options = options || {};
|
|
@@ -27,7 +27,7 @@ module.exports = function (agenda, options) {
|
|
|
27
27
|
throw error;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (!semver.satisfies(serverInfo.version, ">=
|
|
30
|
+
if (!semver.satisfies(semver.coerce(serverInfo.version), ">=3.6.0")) {
|
|
31
31
|
throw new Error("MongoDB version not supported");
|
|
32
32
|
}
|
|
33
33
|
});
|
|
@@ -42,9 +42,7 @@ module.exports = function (agenda, options) {
|
|
|
42
42
|
|
|
43
43
|
if (options.query && options.property) {
|
|
44
44
|
if (options.isObjectId) {
|
|
45
|
-
preMatch[options.property] =
|
|
46
|
-
options.query
|
|
47
|
-
);
|
|
45
|
+
preMatch[options.property] = ObjectId(options.query);
|
|
48
46
|
} else if (/^\d+$/.test(options.query)) {
|
|
49
47
|
preMatch[options.property] = Number.parseInt(options.query, 10);
|
|
50
48
|
} else {
|
|
@@ -286,7 +284,7 @@ module.exports = function (agenda, options) {
|
|
|
286
284
|
const collection = agenda._collection.collection || agenda._collection;
|
|
287
285
|
const jobs = await collection
|
|
288
286
|
.find({
|
|
289
|
-
_id: { $in: jobIds.map((jobId) =>
|
|
287
|
+
_id: { $in: jobIds.map((jobId) => ObjectId(jobId)) },
|
|
290
288
|
})
|
|
291
289
|
.toArray();
|
|
292
290
|
if (jobs.length === 0) {
|
|
@@ -307,9 +305,8 @@ module.exports = function (agenda, options) {
|
|
|
307
305
|
return Promise.reject(new Error("Agenda instance is not ready"));
|
|
308
306
|
}
|
|
309
307
|
|
|
310
|
-
const collection = agenda._collection.collection || agenda._collection;
|
|
311
308
|
return agenda.cancel({
|
|
312
|
-
_id: { $in: jobIds.map((jobId) =>
|
|
309
|
+
_id: { $in: jobIds.map((jobId) => ObjectId(jobId)) },
|
|
313
310
|
});
|
|
314
311
|
};
|
|
315
312
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
module.exports = (agendash) => (instance, opts, done) => {
|
|
4
|
+
const { api, requeueJobs, deleteJobs, createJob } = agendash;
|
|
5
|
+
|
|
6
|
+
instance.register(require("fastify-static"), {
|
|
7
|
+
root: path.join(__dirname, "../../public"),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
instance.get("/", function (req, reply) {
|
|
11
|
+
return reply.sendFile("index.html");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
instance.get("/api", async (request, response) => {
|
|
15
|
+
try {
|
|
16
|
+
const {
|
|
17
|
+
job,
|
|
18
|
+
state,
|
|
19
|
+
skip,
|
|
20
|
+
limit,
|
|
21
|
+
q,
|
|
22
|
+
property,
|
|
23
|
+
isObjectId,
|
|
24
|
+
} = request.query;
|
|
25
|
+
const apiResponse = await api(job, state, {
|
|
26
|
+
query: q,
|
|
27
|
+
property,
|
|
28
|
+
isObjectId,
|
|
29
|
+
skip,
|
|
30
|
+
limit,
|
|
31
|
+
});
|
|
32
|
+
response.send(apiResponse);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
response.status(400).send(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
instance.post("/api/jobs/requeue", async (request, response) => {
|
|
39
|
+
try {
|
|
40
|
+
const newJobs = await requeueJobs(request.body.jobIds);
|
|
41
|
+
response.send(newJobs);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
response.status(404).send(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
instance.post("/api/jobs/delete", async (request, response) => {
|
|
48
|
+
try {
|
|
49
|
+
const deleted = await deleteJobs(request.body.jobIds);
|
|
50
|
+
if (deleted) {
|
|
51
|
+
response.send({ deleted: true });
|
|
52
|
+
} else {
|
|
53
|
+
response.send({ message: "Jobs not deleted" });
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
response.status(404).send(error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
instance.post("/api/jobs/create", async (request, response) => {
|
|
61
|
+
try {
|
|
62
|
+
await createJob(
|
|
63
|
+
request.body.jobName,
|
|
64
|
+
request.body.jobSchedule,
|
|
65
|
+
request.body.jobRepeatEvery,
|
|
66
|
+
request.body.jobData
|
|
67
|
+
);
|
|
68
|
+
response.send({ created: true });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
response.status(400).send(error);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
done();
|
|
75
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agendash",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A modern dashboard for Agenda.js with Pagination and Search capabilities",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": "bin/agendash-standalone.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"entrypoint.sh"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"agenda": "^
|
|
40
|
+
"agenda": "^4.2.1",
|
|
41
41
|
"body-parser": "^1.15.0",
|
|
42
42
|
"commander": "^2.9.0",
|
|
43
43
|
"express": "^4.0.0",
|
|
@@ -45,17 +45,19 @@
|
|
|
45
45
|
"semver": "^7.3.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@hapi/hapi": "
|
|
49
|
-
"@hapi/inert": "6.0.
|
|
48
|
+
"@hapi/hapi": "^20.2.1",
|
|
49
|
+
"@hapi/inert": "^6.0.5",
|
|
50
50
|
"ava": "3.15.0",
|
|
51
51
|
"eslint": "7.19.0",
|
|
52
|
+
"fastify": "^3.24.0",
|
|
53
|
+
"fastify-static": "^4.5.0",
|
|
52
54
|
"koa": "2.13.1",
|
|
53
55
|
"koa-bodyparser": "4.3.0",
|
|
54
56
|
"koa-router": "10.0.0",
|
|
55
57
|
"koa-static": "5.0.0",
|
|
56
58
|
"npm-run-all": "4.1.5",
|
|
57
59
|
"prettier": "2.2.1",
|
|
58
|
-
"supertest": "6.
|
|
60
|
+
"supertest": "^6.2.2"
|
|
59
61
|
},
|
|
60
62
|
"eslintConfig": {
|
|
61
63
|
"parserOptions": {
|
|
@@ -24,7 +24,7 @@ const jobDetail = Vue.component("job-detail", {
|
|
|
24
24
|
<div class="modal-body">
|
|
25
25
|
<div class="row my-3">
|
|
26
26
|
<div class="col">
|
|
27
|
-
<p><strong>Next run starts: </strong>{{ formatDate(job.job.
|
|
27
|
+
<p><strong>Next run starts: </strong>{{ formatDate(job.job.nextRunAt) }}</p>
|
|
28
28
|
<p><strong>Last run started: </strong>{{ formatDate(job.job.lastRunAt) }}</p>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
package/public/app/js/joblist.js
CHANGED
|
@@ -53,9 +53,13 @@ const jobList = Vue.component("job-list", {
|
|
|
53
53
|
cleanMulti() {
|
|
54
54
|
return console.log("received Clean Multi");
|
|
55
55
|
},
|
|
56
|
+
formatTitle(date) {
|
|
57
|
+
if (!date) return;
|
|
58
|
+
return moment(date).format();
|
|
59
|
+
},
|
|
56
60
|
formatDate(date) {
|
|
61
|
+
if (!date) return;
|
|
57
62
|
return moment(date).fromNow();
|
|
58
|
-
// return new Date(date).toLocaleDateString('en-US', { day: 'numeric', month: 'numeric', year: '2-digit', hour: "numeric", minute: "numeric", second: "numeric" })
|
|
59
63
|
},
|
|
60
64
|
checkAllCheckboxes() {
|
|
61
65
|
const checkboxes = document.querySelectorAll(".checkbox-triggerable");
|
|
@@ -139,10 +143,10 @@ const jobList = Vue.component("job-list", {
|
|
|
139
143
|
<i v-if="job.running" class="pill-own bg-warning pill-withoutIcon"><span>Running</span></i>
|
|
140
144
|
</td>
|
|
141
145
|
<td class="job-name" @click="toggleList(job)"> {{job.job.name}} </td>
|
|
142
|
-
<td class="job-lastRunAt"
|
|
143
|
-
<td class="job-nextRunAt"
|
|
144
|
-
<td class="job-finishedAt"
|
|
145
|
-
<td class="job-lockedAt"
|
|
146
|
+
<td class="job-lastRunAt" :title="formatTitle(job.job.lastRunAt)" @click="toggleList(job)"> {{ formatDate(job.job.lastRunAt) }} </td>
|
|
147
|
+
<td class="job-nextRunAt" :title="formatTitle(job.job.nextRunAt)" @click="toggleList(job)"> {{ formatDate(job.job.nextRunAt) }} </td>
|
|
148
|
+
<td class="job-finishedAt" :title="formatTitle(job.job.lastFinishedAt)" @click="toggleList(job)"> {{ formatDate(job.job.lastFinishedAt) }} </td>
|
|
149
|
+
<td class="job-lockedAt" :title="formatTitle(job.job.lockedAt)" @click="toggleList(job)"> {{ formatDate(job.job.lockedAt) }} </td>
|
|
146
150
|
<td class="job-actions">
|
|
147
151
|
<i class="material-icons md-dark md-custom action-btn viewData text-primary" data-toggle="modal" data-target="#modalRequeueSure" @click="$emit('confirm-requeue', job)" data-placement="left" title="Requeue">update</i>
|
|
148
152
|
<i class="material-icons md-dark md-custom action-btn viewData text-success" data-toggle="modal" data-target="#modalData" @click="$emit('show-job-detail', job)" data-placement="top" title="Job Data">visibility</i>
|
|
@@ -184,13 +188,13 @@ const jobList = Vue.component("job-list", {
|
|
|
184
188
|
<div class="card-responsive-status-title">
|
|
185
189
|
Last run started
|
|
186
190
|
</div>
|
|
187
|
-
<div class="mb-3">
|
|
191
|
+
<div class="mb-3" :title="formatTitle(job.job.lastRunAt)">
|
|
188
192
|
{{ formatDate(job.job.lastRunAt) }}
|
|
189
193
|
</div>
|
|
190
194
|
<div class="card-responsive-status-title">
|
|
191
195
|
Last finished
|
|
192
196
|
</div>
|
|
193
|
-
<div>
|
|
197
|
+
<div :title="formatTitle(job.job.lastFinishedAt)">
|
|
194
198
|
{{ formatDate(job.job.lastFinishedAt) }}
|
|
195
199
|
</div>
|
|
196
200
|
</div>
|
|
@@ -198,14 +202,14 @@ const jobList = Vue.component("job-list", {
|
|
|
198
202
|
<div class="card-responsive-status-title">
|
|
199
203
|
Next run starts
|
|
200
204
|
</div>
|
|
201
|
-
<div class="mb-3">
|
|
205
|
+
<div class="mb-3" :title="formatTitle(job.job.nextRunAt)">
|
|
202
206
|
{{ formatDate(job.job.nextRunAt) }}
|
|
203
207
|
</div>
|
|
204
208
|
<div class="card-responsive-status-title">
|
|
205
209
|
Locked
|
|
206
210
|
</div>
|
|
207
|
-
<div>
|
|
208
|
-
{{
|
|
211
|
+
<div :title="formatTitle(job.job.lockedAt)">
|
|
212
|
+
{{ formatDate(job.job.lockedAt) || "-" }}
|
|
209
213
|
</div>
|
|
210
214
|
</div>
|
|
211
215
|
</div>
|