agendash 3.0.0 → 4.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 +12 -0
- package/bin/agendash-standalone-fastify.js +1 -1
- package/lib/controllers/agendash.js +4 -3
- package/lib/csp.js +28 -0
- package/lib/middlewares/express.js +7 -0
- package/lib/middlewares/fastify.js +3 -1
- package/lib/middlewares/hapi.js +6 -0
- package/lib/middlewares/koa.js +6 -0
- package/package.json +6 -6
- package/public/app/css/styles.css +13 -1
- package/public/app/js/jobdetail.js +1 -1
- package/public/app/js/joblist.js +15 -6
- package/public/app/js/main.js +21 -13
- package/public/app/js/sidebar.js +1 -1
- package/public/app/js/topbar.js +2 -2
package/History.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# 4.0.0
|
|
2
|
+
|
|
3
|
+
- Drop support for node 12. Add support for node 18 and MongoDB 5.0.
|
|
4
|
+
- Fastify upgraded to v4. #215
|
|
5
|
+
- Various UI fixes. #212 #203
|
|
6
|
+
- Vulnerability fixes. #220
|
|
7
|
+
- Requeue and Delete job fixes. #226 #227
|
|
8
|
+
|
|
9
|
+
# 3.1.0
|
|
10
|
+
|
|
11
|
+
- Add Content-Security-Policy header to all server implementations
|
|
12
|
+
|
|
1
13
|
# 3.0.0
|
|
2
14
|
|
|
3
15
|
- BREAKING: Drop support for MongoDB v3.4, only >=v3.6 is supported now.
|
|
@@ -42,7 +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] = ObjectId(options.query);
|
|
45
|
+
preMatch[options.property] = new ObjectId(options.query);
|
|
46
46
|
} else if (/^\d+$/.test(options.query)) {
|
|
47
47
|
preMatch[options.property] = Number.parseInt(options.query, 10);
|
|
48
48
|
} else {
|
|
@@ -61,6 +61,7 @@ module.exports = function (agenda, options) {
|
|
|
61
61
|
{ $match: preMatch },
|
|
62
62
|
{
|
|
63
63
|
$sort: {
|
|
64
|
+
repeatInterval: -1,
|
|
64
65
|
nextRunAt: -1,
|
|
65
66
|
lastRunAt: -1,
|
|
66
67
|
lastFinishedAt: -1,
|
|
@@ -284,7 +285,7 @@ module.exports = function (agenda, options) {
|
|
|
284
285
|
const collection = agenda._collection.collection || agenda._collection;
|
|
285
286
|
const jobs = await collection
|
|
286
287
|
.find({
|
|
287
|
-
_id: { $in: jobIds.map((jobId) => ObjectId(jobId)) },
|
|
288
|
+
_id: { $in: jobIds.map((jobId) => new ObjectId(jobId)) },
|
|
288
289
|
})
|
|
289
290
|
.toArray();
|
|
290
291
|
if (jobs.length === 0) {
|
|
@@ -306,7 +307,7 @@ module.exports = function (agenda, options) {
|
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
return agenda.cancel({
|
|
309
|
-
_id: { $in: jobIds.map((jobId) => ObjectId(jobId)) },
|
|
310
|
+
_id: { $in: jobIds.map((jobId) => new ObjectId(jobId)) },
|
|
310
311
|
});
|
|
311
312
|
};
|
|
312
313
|
|
package/lib/csp.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const CSP = {
|
|
2
|
+
"default-src": [
|
|
3
|
+
"'self'",
|
|
4
|
+
],
|
|
5
|
+
"script-src": [
|
|
6
|
+
"https://code.jquery.com",
|
|
7
|
+
"https://cdn.jsdelivr.net",
|
|
8
|
+
"https://cdnjs.cloudflare.com",
|
|
9
|
+
"https://stackpath.bootstrapcdn.com",
|
|
10
|
+
"'unsafe-inline'",
|
|
11
|
+
"'unsafe-eval'",
|
|
12
|
+
"'self'",
|
|
13
|
+
],
|
|
14
|
+
"style-src": [
|
|
15
|
+
"https://cdn.jsdelivr.net",
|
|
16
|
+
"https://stackpath.bootstrapcdn.com",
|
|
17
|
+
"https://fonts.googleapis.com",
|
|
18
|
+
"https://unpkg.com",
|
|
19
|
+
"'self'",
|
|
20
|
+
],
|
|
21
|
+
"font-src": [
|
|
22
|
+
"https://fonts.gstatic.com",
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = Object.entries(CSP)
|
|
27
|
+
.map(([type, values]) => `${type} ${values.join(" ")}`)
|
|
28
|
+
.join("; ")
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const express = require("express");
|
|
3
3
|
const bodyParser = require("body-parser");
|
|
4
|
+
const csp = require("../csp");
|
|
4
5
|
|
|
5
6
|
module.exports = (agendash) => {
|
|
6
7
|
const { api, requeueJobs, deleteJobs, createJob } = agendash;
|
|
7
8
|
const app = express();
|
|
8
9
|
app.use(bodyParser.json());
|
|
9
10
|
app.use(bodyParser.urlencoded({ extended: false }));
|
|
11
|
+
|
|
12
|
+
app.use((req, res, next) => {
|
|
13
|
+
res.header("Content-Security-Policy", csp);
|
|
14
|
+
next();
|
|
15
|
+
});
|
|
16
|
+
|
|
10
17
|
app.use("/", express.static(path.join(__dirname, "../../public")));
|
|
11
18
|
|
|
12
19
|
app.get("/api", async (request, response) => {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const csp = require("../csp");
|
|
2
3
|
|
|
3
4
|
module.exports = (agendash) => (instance, opts, done) => {
|
|
4
5
|
const { api, requeueJobs, deleteJobs, createJob } = agendash;
|
|
5
6
|
|
|
6
|
-
instance.register(require("fastify
|
|
7
|
+
instance.register(require("@fastify/static"), {
|
|
7
8
|
root: path.join(__dirname, "../../public"),
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
instance.get("/", function (req, reply) {
|
|
12
|
+
reply.header("Content-Security-Policy", csp);
|
|
11
13
|
return reply.sendFile("index.html");
|
|
12
14
|
});
|
|
13
15
|
|
package/lib/middlewares/hapi.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const pack = require("../../package.json");
|
|
3
|
+
const csp = require("../csp");
|
|
3
4
|
|
|
4
5
|
module.exports = (agendash) => {
|
|
5
6
|
const { api, requeueJobs, deleteJobs, createJob } = agendash;
|
|
6
7
|
return {
|
|
7
8
|
pkg: pack,
|
|
8
9
|
register: (server, options) => {
|
|
10
|
+
server.ext("onPreResponse", (req, h) => {
|
|
11
|
+
req.response.header("Content-Security-Policy", csp);
|
|
12
|
+
return h.continue;
|
|
13
|
+
});
|
|
14
|
+
|
|
9
15
|
server.route([
|
|
10
16
|
{
|
|
11
17
|
method: "GET",
|
package/lib/middlewares/koa.js
CHANGED
|
@@ -2,10 +2,16 @@ const path = require("path");
|
|
|
2
2
|
const bodyParser = require("koa-bodyparser");
|
|
3
3
|
const Router = require("koa-router");
|
|
4
4
|
const koaStatic = require("koa-static");
|
|
5
|
+
const csp = require("../csp");
|
|
5
6
|
|
|
6
7
|
module.exports = (agendash) => {
|
|
7
8
|
const middlewares = [];
|
|
8
9
|
|
|
10
|
+
middlewares.push(async (ctx, next) => {
|
|
11
|
+
await next();
|
|
12
|
+
ctx.set("Content-Security-Policy", csp);
|
|
13
|
+
});
|
|
14
|
+
|
|
9
15
|
middlewares.push(
|
|
10
16
|
koaStatic(path.resolve(__dirname, "../../public"), { defer: true })
|
|
11
17
|
);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agendash",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.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",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=14.0.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "run-p lint ava",
|
|
@@ -38,19 +38,19 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"agenda": "^4.2.1",
|
|
41
|
-
"body-parser": "^1.
|
|
41
|
+
"body-parser": "^1.19.2",
|
|
42
42
|
"commander": "^2.9.0",
|
|
43
|
-
"express": "^4.
|
|
43
|
+
"express": "^4.17.3",
|
|
44
44
|
"mongodb": "*",
|
|
45
45
|
"semver": "^7.3.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
+
"@fastify/static": "^6.5.0",
|
|
48
49
|
"@hapi/hapi": "^20.2.1",
|
|
49
50
|
"@hapi/inert": "^6.0.5",
|
|
50
51
|
"ava": "3.15.0",
|
|
51
52
|
"eslint": "7.19.0",
|
|
52
|
-
"fastify": "^
|
|
53
|
-
"fastify-static": "^4.5.0",
|
|
53
|
+
"fastify": "^4.9.2",
|
|
54
54
|
"koa": "2.13.1",
|
|
55
55
|
"koa-bodyparser": "4.3.0",
|
|
56
56
|
"koa-router": "10.0.0",
|
|
@@ -122,8 +122,20 @@ code {
|
|
|
122
122
|
margin: 0 2px;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
.job-detail-dialog {
|
|
126
|
+
max-width: calc(100vw - 1.75rem * 2);
|
|
127
|
+
}
|
|
128
|
+
.modal-body > * {
|
|
129
|
+
flex: 0 1 auto;
|
|
130
|
+
}
|
|
131
|
+
.modal-body {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-flow: column;
|
|
134
|
+
max-height: calc(100vh - 195px);
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
.json-editor {
|
|
126
|
-
|
|
138
|
+
flex: 1 1 auto;
|
|
127
139
|
}
|
|
128
140
|
.card-responsive-title-container {
|
|
129
141
|
display: flex;
|
|
@@ -13,7 +13,7 @@ const jobDetail = Vue.component("job-detail", {
|
|
|
13
13
|
template: `
|
|
14
14
|
<div class="modal fade" id="modalData" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
|
15
15
|
<!-- Modal -->
|
|
16
|
-
<div class="modal-dialog" role="document">
|
|
16
|
+
<div class="modal-dialog job-detail-dialog" role="document">
|
|
17
17
|
<div class="modal-content">
|
|
18
18
|
<div class="modal-header">
|
|
19
19
|
<h5 class="modal-title" id="exampleModalLabel">Job Data - {{job.job.name}}</h5>
|
package/public/app/js/joblist.js
CHANGED
|
@@ -4,10 +4,10 @@ const jobList = Vue.component("job-list", {
|
|
|
4
4
|
currentSort: "name",
|
|
5
5
|
currentSortDir: "asc",
|
|
6
6
|
}),
|
|
7
|
-
props: ["jobs", "pagesize", "pagenumber", "sendClean", "loading"],
|
|
7
|
+
props: ["jobs", "pagesize", "pagenumber", "totalPages", "sendClean", "loading"],
|
|
8
8
|
computed: {
|
|
9
9
|
sortedJobs: function () {
|
|
10
|
-
|
|
10
|
+
var sortedJobs = this.jobs.sort((a, b) => {
|
|
11
11
|
let displayA, displayB;
|
|
12
12
|
if (this.currentSort === "name") {
|
|
13
13
|
displayA = a.job[this.currentSort]
|
|
@@ -26,6 +26,12 @@ const jobList = Vue.component("job-list", {
|
|
|
26
26
|
if (displayA > displayB) return 1 * modifier;
|
|
27
27
|
return 0;
|
|
28
28
|
});
|
|
29
|
+
|
|
30
|
+
/** Show recurring jobs first */
|
|
31
|
+
return Array.prototype.concat(
|
|
32
|
+
sortedJobs.filter(job => job.repeating === true),
|
|
33
|
+
sortedJobs.filter(job => job.repeating === false),
|
|
34
|
+
)
|
|
29
35
|
},
|
|
30
36
|
},
|
|
31
37
|
watch: {
|
|
@@ -226,14 +232,17 @@ const jobList = Vue.component("job-list", {
|
|
|
226
232
|
<nav aria-label="Page navigation example">
|
|
227
233
|
<ul class="pagination">
|
|
228
234
|
<li class="page-item" :class="pagenumber === 1 ? 'disabled': ''"><a class="page-link" @click="$emit('pagechange', 'prev')">Previous</a></li>
|
|
229
|
-
|
|
230
|
-
<li class="page-item active"><a class="page-link">{{pagenumber}}</a></li>
|
|
231
|
-
<li class="page-item"><a style="cursor:pointer;" class="page-link" @click="$emit('pagechange', 'next')">{{pagenumber +1}}</a></li> -->
|
|
232
|
-
<li class="page-item"><a style="cursor:pointer;" class="page-link" @click="$emit('pagechange', 'next')">Next</a></li>
|
|
235
|
+
<li class="page-item" :class="pagenumber >= totalPages ? 'disabled': ''"> <a style="cursor:pointer;" class="page-link" @click="$emit('pagechange', 'next')">Next</a> </li>
|
|
233
236
|
</ul>
|
|
234
237
|
</nav>
|
|
235
238
|
</div>
|
|
236
239
|
</div>
|
|
240
|
+
|
|
241
|
+
<div class="row">
|
|
242
|
+
<div class="col d-flex justify-content-center">
|
|
243
|
+
Page: {{pagenumber}} / {{totalPages}}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
237
246
|
</div>
|
|
238
247
|
`,
|
|
239
248
|
});
|
package/public/app/js/main.js
CHANGED
|
@@ -2,9 +2,10 @@ const app = Vue.component("app", {
|
|
|
2
2
|
data: () => ({
|
|
3
3
|
jobs: [],
|
|
4
4
|
overview: [],
|
|
5
|
-
refresh:
|
|
5
|
+
refresh: 30,
|
|
6
6
|
showDetail: false,
|
|
7
7
|
pagenumber: 1,
|
|
8
|
+
totalPages: 0,
|
|
8
9
|
showConfirm: false,
|
|
9
10
|
showConfirmMulti: false,
|
|
10
11
|
showConfirmRequeue: false,
|
|
@@ -13,7 +14,7 @@ const app = Vue.component("app", {
|
|
|
13
14
|
jobData: {},
|
|
14
15
|
deletec: false,
|
|
15
16
|
requeuec: false,
|
|
16
|
-
pagesize:
|
|
17
|
+
pagesize: 50,
|
|
17
18
|
sendClean: false,
|
|
18
19
|
createc: false,
|
|
19
20
|
property: "",
|
|
@@ -66,14 +67,19 @@ const app = Vue.component("app", {
|
|
|
66
67
|
this.showNewJob = true;
|
|
67
68
|
},
|
|
68
69
|
searchForm(name, search, property, limit, skip, refresh, state, object) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
this.pagesize = limit ? limit : this.pagesize
|
|
71
|
+
this.name = name
|
|
72
|
+
this.search = search
|
|
73
|
+
this.property = property
|
|
74
|
+
this.skip = skip
|
|
75
|
+
this.refresh = refresh
|
|
76
|
+
this.state = state
|
|
77
|
+
this.object = object ? object : this.object
|
|
78
|
+
|
|
79
|
+
// Form changed, reset the pagination state
|
|
80
|
+
this.pagenumber = 1
|
|
81
|
+
this.totalPages = 1
|
|
82
|
+
|
|
77
83
|
this.fetchData(
|
|
78
84
|
this.name,
|
|
79
85
|
this.search,
|
|
@@ -120,9 +126,9 @@ const app = Vue.component("app", {
|
|
|
120
126
|
name = "",
|
|
121
127
|
search = "",
|
|
122
128
|
property = "",
|
|
123
|
-
limit =
|
|
129
|
+
limit = 50,
|
|
124
130
|
skip = 0,
|
|
125
|
-
refresh =
|
|
131
|
+
refresh = 30,
|
|
126
132
|
state = "",
|
|
127
133
|
object
|
|
128
134
|
) {
|
|
@@ -143,6 +149,7 @@ const app = Vue.component("app", {
|
|
|
143
149
|
this.object = object;
|
|
144
150
|
this.overview = data.overview;
|
|
145
151
|
this.loading = false;
|
|
152
|
+
this.totalPages = data.totalPages;
|
|
146
153
|
},
|
|
147
154
|
() => {
|
|
148
155
|
this.loading = false;
|
|
@@ -229,7 +236,7 @@ const app = Vue.component("app", {
|
|
|
229
236
|
>
|
|
230
237
|
</sidebar>
|
|
231
238
|
</div>
|
|
232
|
-
<main role="main" class="col-md-10 ml-sm-auto col-lg-10 px-4">
|
|
239
|
+
<main role="main" class="col-md-10 ml-sm-auto col-lg-10 px-4 pt-3 pb-5">
|
|
233
240
|
<div class="col-12">
|
|
234
241
|
<topbar v-on:search-form="searchForm"
|
|
235
242
|
:name='name'
|
|
@@ -249,6 +256,7 @@ const app = Vue.component("app", {
|
|
|
249
256
|
v-on:pagechange="pagechange"
|
|
250
257
|
:pagesize="pagesize"
|
|
251
258
|
:pagenumber='pagenumber'
|
|
259
|
+
:totalPages='totalPages'
|
|
252
260
|
:skip="skip"
|
|
253
261
|
:jobs="jobs"
|
|
254
262
|
:sendClean='sendClean'
|
package/public/app/js/sidebar.js
CHANGED
|
@@ -63,7 +63,7 @@ const sidebar = Vue.component("sidebar", {
|
|
|
63
63
|
},
|
|
64
64
|
},
|
|
65
65
|
template: `
|
|
66
|
-
<div class="col sidebar">
|
|
66
|
+
<div class="col sidebar pt-4">
|
|
67
67
|
<div class="row">
|
|
68
68
|
<div class="col ">
|
|
69
69
|
<button data-toggle="modal" data-target="#modalNewJob" @click="$emit('new-job')" data-placement="top" title="Add a new job" class="btn btn-block btn-outline-success"><i class="oi oi-plus IcoInButton"></i> New Job</button>
|