jetcheck 1.1.4 → 1.1.6
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/config-example.js +2 -0
- package/config-simple.js +17 -17
- package/package.json +2 -2
- package/services/builtin/cmd.js +0 -2
- package/services/builtin/email.js +50 -1
- package/services/builtin/http.js +105 -13
- package/services/builtin/ovh.js +0 -1
- package/services/builtin/ping.js +10 -7
- package/services/client.js +101 -40
- package/services/tests.js +5 -2
package/config-example.js
CHANGED
|
@@ -40,8 +40,10 @@ module.exports = {
|
|
|
40
40
|
// checker tasks
|
|
41
41
|
tasks: [
|
|
42
42
|
// { type: "ping", host: "8.8.8.8", count: 2 },
|
|
43
|
+
// { type: "ping", host: "8.8.8.8", interface: "eth0" },
|
|
43
44
|
{ type: "ping", host: "54.43.5.5" },
|
|
44
45
|
// { type: "http", url: "https://8.8.8.8" },
|
|
46
|
+
// { type: "http", url: "https://1.1.1.1", interface: "eth0" },
|
|
45
47
|
// { type: "http", url: "https://1.1.1.1" },
|
|
46
48
|
// { type: "http", url: "https://1.2.4.1" },
|
|
47
49
|
],
|
package/config-simple.js
CHANGED
|
@@ -35,7 +35,7 @@ module.exports = {
|
|
|
35
35
|
init: "DEGRADED",
|
|
36
36
|
|
|
37
37
|
// test every 15 seconds
|
|
38
|
-
interval:
|
|
38
|
+
interval: 5,
|
|
39
39
|
|
|
40
40
|
// checker tasks
|
|
41
41
|
tasks: [
|
|
@@ -54,17 +54,17 @@ module.exports = {
|
|
|
54
54
|
content: {
|
|
55
55
|
from: "alert@tc.be-ys.io",
|
|
56
56
|
to: "m.vergoz@protonmail.com",
|
|
57
|
-
subject: "[FAILED] Dev test",
|
|
58
|
-
text: `Hi, the 'Dev test' fails at ${new Date().toLocaleString()}`
|
|
57
|
+
// subject: "[FAILED] Dev test",
|
|
58
|
+
// text: `Hi, the 'Dev test' fails at ${new Date().toLocaleString()}`
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},
|
|
62
|
+
// {
|
|
63
|
+
// type: "ovh.dns.zone.unset",
|
|
64
|
+
// account: OVH_BeysIO,
|
|
65
|
+
// zone: "be-ys.io",
|
|
66
|
+
// config: OVH_BeysIO_DNS
|
|
67
|
+
// },
|
|
68
68
|
|
|
69
69
|
{ type: "cmd", cmd: `logger "TEST: Disconnected from HTTP"` }
|
|
70
70
|
],
|
|
@@ -77,17 +77,17 @@ module.exports = {
|
|
|
77
77
|
content: {
|
|
78
78
|
from: "alert@tc.be-ys.io",
|
|
79
79
|
to: "m.vergoz@protonmail.com",
|
|
80
|
-
subject: "[RESOLVED] Dev test",
|
|
81
|
-
text: `Hi, the 'Dev test' has been resolved at ${new Date().toLocaleString()}`
|
|
80
|
+
// subject: "[RESOLVED] Dev test",
|
|
81
|
+
// text: `Hi, the 'Dev test' has been resolved at ${new Date().toLocaleString()}`
|
|
82
82
|
}
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
85
|
+
// {
|
|
86
|
+
// type: "ovh.dns.zone.set",
|
|
87
|
+
// account: OVH_BeysIO,
|
|
88
|
+
// zone: "be-ys.io",
|
|
89
|
+
// config: OVH_BeysIO_DNS
|
|
90
|
+
// },
|
|
91
91
|
|
|
92
92
|
{ type: "cmd", cmd: `logger "TEST: Connected from HTTP"` },
|
|
93
93
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jetcheck",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Javascript based Network & System checker",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"author": "Michael VERGOZ",
|
|
14
14
|
"license": "GPL-3.0",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"axios": "^1.7.9",
|
|
17
16
|
"body-parser": "^1.20.3",
|
|
18
17
|
"ejs": "^3.1.10",
|
|
18
|
+
"elapsed": "^0.0.7",
|
|
19
19
|
"express": "^4.21.2",
|
|
20
20
|
"mkdirp": "^3.0.1",
|
|
21
21
|
"nodemailer": "^6.9.16",
|
package/services/builtin/cmd.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const NodeMailer = require("nodemailer")
|
|
2
|
+
const Elapsed = require("elapsed")
|
|
2
3
|
|
|
3
4
|
async function sender(options) {
|
|
4
5
|
const ret = {}
|
|
@@ -20,7 +21,55 @@ module.exports = async function (kernel) {
|
|
|
20
21
|
return (`email(${options.content?.to})`)
|
|
21
22
|
},
|
|
22
23
|
trigger: async function (task, state) {
|
|
23
|
-
|
|
24
|
+
const options = { ...task }
|
|
25
|
+
|
|
26
|
+
if (!task.content)
|
|
27
|
+
task.content = {}
|
|
28
|
+
|
|
29
|
+
options.content = { ...task.content }
|
|
30
|
+
|
|
31
|
+
// manage auto subject
|
|
32
|
+
if (!task.content.subject) {
|
|
33
|
+
if (state.mode === "DEGRADED")
|
|
34
|
+
options.content.subject = `[RESOLVED] - ${state.name}`
|
|
35
|
+
else if (state.mode === "WORKING")
|
|
36
|
+
options.content.subject = `[FAILED] - ${state.name}`
|
|
37
|
+
}
|
|
38
|
+
else if (typeof task.subject === 'function')
|
|
39
|
+
options.content.subject = task.subject(options, state)
|
|
40
|
+
|
|
41
|
+
// manage email content
|
|
42
|
+
if (!task.content.text) {
|
|
43
|
+
const deadTime = state.hasOwnProperty("deadTime") ? state.deadTime : new Date
|
|
44
|
+
if (state.mode === "DEGRADED")
|
|
45
|
+
options.content.text = [
|
|
46
|
+
`Hi,`,
|
|
47
|
+
'',
|
|
48
|
+
`The test '${state.name}' has been resolved at ${new Date().toLocaleString()}`,
|
|
49
|
+
`\t- Detection time: ${deadTime.toLocaleString()}`,
|
|
50
|
+
`\t- Outage elapsed: ${(new Elapsed(deadTime)).optimal}`,
|
|
51
|
+
].join("\n")
|
|
52
|
+
else if (state.mode === "WORKING")
|
|
53
|
+
options.content.text = [
|
|
54
|
+
`Hi,`,
|
|
55
|
+
'',
|
|
56
|
+
`The test '${state.name}' fails at ${new Date().toLocaleString()}`,
|
|
57
|
+
].join("\n")
|
|
58
|
+
}
|
|
59
|
+
else if (typeof task.text === 'function')
|
|
60
|
+
options.content.text = task.text(options, state)
|
|
61
|
+
|
|
62
|
+
if(!options.content.text)
|
|
63
|
+
options.content.text = ""
|
|
64
|
+
|
|
65
|
+
options.content.text = [
|
|
66
|
+
options.content.text,
|
|
67
|
+
"",
|
|
68
|
+
"--",
|
|
69
|
+
`Jetcheck v${kernel.version}`
|
|
70
|
+
].join("\n")
|
|
71
|
+
|
|
72
|
+
await sender(options)
|
|
24
73
|
}
|
|
25
74
|
}
|
|
26
75
|
console.log("Loading Email plugin")
|
package/services/builtin/http.js
CHANGED
|
@@ -1,24 +1,116 @@
|
|
|
1
|
-
const
|
|
1
|
+
const http = require("http")
|
|
2
|
+
const https = require("https")
|
|
3
|
+
const net = require("net")
|
|
4
|
+
const os = require("os")
|
|
5
|
+
|
|
6
|
+
const REDIRECT = new Set([301, 302, 303, 307, 308])
|
|
7
|
+
|
|
8
|
+
function familyOf(entry) {
|
|
9
|
+
if (entry.family === 6 || entry.family === "IPv6")
|
|
10
|
+
return 6
|
|
11
|
+
if (entry.family === 4 || entry.family === "IPv4")
|
|
12
|
+
return 4
|
|
13
|
+
return 0
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isLinkLocal(address, family) {
|
|
17
|
+
if (family === 6)
|
|
18
|
+
return address.toLowerCase().startsWith("fe80:")
|
|
19
|
+
if (family === 4)
|
|
20
|
+
return address.startsWith("169.254.")
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sourceAddress(iface, prefer) {
|
|
25
|
+
const list = os.networkInterfaces()[iface]
|
|
26
|
+
if (!list || list.length === 0)
|
|
27
|
+
throw new Error(`Network interface not found "${iface}"`)
|
|
28
|
+
|
|
29
|
+
const families = prefer ? [prefer] : [4, 6]
|
|
30
|
+
for (const family of families) {
|
|
31
|
+
const matches = list.filter((entry) => familyOf(entry) === family)
|
|
32
|
+
if (matches.length === 0)
|
|
33
|
+
continue
|
|
34
|
+
const usable = matches.filter((entry) => !isLinkLocal(entry.address, family))
|
|
35
|
+
const pool = usable.length ? usable : matches
|
|
36
|
+
const picked = pool.find((entry) => !entry.internal) || pool[0]
|
|
37
|
+
return { address: picked.address, family }
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`No usable address on interface "${iface}"`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function request(url, timeout, source) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let settled = false
|
|
45
|
+
const fail = (err) => {
|
|
46
|
+
if (settled)
|
|
47
|
+
return
|
|
48
|
+
settled = true
|
|
49
|
+
reject(err)
|
|
50
|
+
}
|
|
51
|
+
const ok = (value) => {
|
|
52
|
+
if (settled)
|
|
53
|
+
return
|
|
54
|
+
settled = true
|
|
55
|
+
resolve(value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const target = new URL(url)
|
|
59
|
+
const lib = target.protocol === "https:" ? https : http
|
|
60
|
+
const options = {
|
|
61
|
+
protocol: target.protocol,
|
|
62
|
+
hostname: target.hostname,
|
|
63
|
+
port: target.port || undefined,
|
|
64
|
+
path: `${target.pathname}${target.search}`,
|
|
65
|
+
method: "GET",
|
|
66
|
+
}
|
|
67
|
+
if (source) {
|
|
68
|
+
options.localAddress = source.address
|
|
69
|
+
options.family = source.family
|
|
70
|
+
}
|
|
71
|
+
const req = lib.request(options, (res) => {
|
|
72
|
+
res.on("error", fail)
|
|
73
|
+
res.on("data", () => { })
|
|
74
|
+
res.on("end", () => ok(res))
|
|
75
|
+
})
|
|
76
|
+
req.setTimeout(timeout, () => {
|
|
77
|
+
req.destroy(new Error(`timeout of ${timeout}ms exceeded`))
|
|
78
|
+
})
|
|
79
|
+
req.on("error", fail)
|
|
80
|
+
req.end()
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function check(url, timeout, source, redirectsLeft = 5) {
|
|
85
|
+
const res = await request(url, timeout, source)
|
|
86
|
+
if (REDIRECT.has(res.statusCode) && res.headers.location) {
|
|
87
|
+
if (redirectsLeft <= 0)
|
|
88
|
+
throw new Error("Maximum number of redirects exceeded")
|
|
89
|
+
const next = new URL(res.headers.location, url).toString()
|
|
90
|
+
return check(next, timeout, source, redirectsLeft - 1)
|
|
91
|
+
}
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
2
94
|
|
|
3
95
|
module.exports = async function (kernel) {
|
|
4
96
|
kernel.tests.plugins.http = {
|
|
5
97
|
toString: function (options) {
|
|
6
|
-
|
|
98
|
+
const via = options.interface ? ` via ${options.interface}` : ""
|
|
99
|
+
return (`http(${options.url}${via})`)
|
|
7
100
|
},
|
|
8
101
|
run: async function (options, task, state) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} catch (error) {
|
|
17
|
-
return error.message;
|
|
102
|
+
const timeout = options.timeout ? options.timeout : 5000
|
|
103
|
+
try {
|
|
104
|
+
let source = null
|
|
105
|
+
if (options.interface) {
|
|
106
|
+
const hostname = new URL(options.url).hostname
|
|
107
|
+
const prefer = net.isIP(hostname)
|
|
108
|
+
source = sourceAddress(options.interface, prefer)
|
|
18
109
|
}
|
|
110
|
+
return await check(options.url, timeout, source)
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return error.message
|
|
19
113
|
}
|
|
20
|
-
return (await check(options.url))
|
|
21
|
-
|
|
22
114
|
}
|
|
23
115
|
}
|
|
24
116
|
console.log("Loading HTTP plugin")
|
package/services/builtin/ovh.js
CHANGED
package/services/builtin/ping.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
const axios = require('axios');
|
|
2
|
-
|
|
3
1
|
module.exports = async function (kernel) {
|
|
4
2
|
kernel.tests.plugins.ping = {
|
|
5
3
|
toString: function (options) {
|
|
6
|
-
|
|
4
|
+
const via = options.interface ? ` via ${options.interface}` : ""
|
|
5
|
+
return (`ping(${options.host}${via})`)
|
|
7
6
|
},
|
|
8
7
|
run: async function (options, task, state) {
|
|
9
8
|
const count = options.count ? options.count : 3
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
// macOS binds the socket with -b. Linux accepts the interface name with -I.
|
|
10
|
+
const bind = options.interface
|
|
11
|
+
? (process.platform === "darwin" ? `-b ${options.interface} ` : `-I ${options.interface} `)
|
|
12
|
+
: ""
|
|
13
|
+
const ret = await kernel.lib.exec(`ping -c ${count} ${bind}${options.host}`)
|
|
14
|
+
if (ret.error > 0)
|
|
15
|
+
return (ret.stderr || "Host down")
|
|
16
|
+
return (null)
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
console.log("Loading PING plugin")
|
package/services/client.js
CHANGED
|
@@ -1,57 +1,118 @@
|
|
|
1
|
-
const
|
|
1
|
+
const http = require('http')
|
|
2
2
|
const https = require('https')
|
|
3
|
-
const
|
|
4
|
-
const os = require('os')
|
|
3
|
+
const os = require('os')
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.account = account;
|
|
10
|
-
this.endpoint = "/api/v1"
|
|
5
|
+
function joinURL(base, path) {
|
|
6
|
+
return base.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '')
|
|
7
|
+
}
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
9
|
+
function request(method, url, { headers, body, timeout, agent }) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
let settled = false
|
|
12
|
+
const fail = (err) => {
|
|
13
|
+
if (settled)
|
|
14
|
+
return
|
|
15
|
+
settled = true
|
|
16
|
+
reject(err)
|
|
17
|
+
}
|
|
18
|
+
const ok = (value) => {
|
|
19
|
+
if (settled)
|
|
20
|
+
return
|
|
21
|
+
settled = true
|
|
22
|
+
resolve(value)
|
|
23
|
+
}
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const target = new URL(url)
|
|
26
|
+
const lib = target.protocol === 'https:' ? https : http
|
|
27
|
+
const payload = body == null ? null : Buffer.from(JSON.stringify(body))
|
|
28
|
+
const reqHeaders = { ...headers }
|
|
29
|
+
if (payload) {
|
|
30
|
+
reqHeaders['Content-Type'] = 'application/json'
|
|
31
|
+
reqHeaders['Content-Length'] = String(payload.length)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const req = lib.request({
|
|
35
|
+
protocol: target.protocol,
|
|
36
|
+
hostname: target.hostname,
|
|
37
|
+
port: target.port || undefined,
|
|
38
|
+
path: `${target.pathname}${target.search}`,
|
|
39
|
+
method,
|
|
40
|
+
headers: reqHeaders,
|
|
41
|
+
agent: target.protocol === 'https:' ? agent : undefined,
|
|
42
|
+
}, (res) => {
|
|
43
|
+
const chunks = []
|
|
44
|
+
res.on('data', (chunk) => chunks.push(chunk))
|
|
45
|
+
res.on('error', fail)
|
|
46
|
+
res.on('end', () => {
|
|
47
|
+
const raw = Buffer.concat(chunks).toString('utf8')
|
|
48
|
+
const type = String(res.headers['content-type'] || '')
|
|
49
|
+
if (!type.includes('application/json')) {
|
|
50
|
+
ok({ status: res.statusCode, data: raw })
|
|
31
51
|
return
|
|
32
52
|
}
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
try {
|
|
54
|
+
ok({ status: res.statusCode, data: raw.length ? JSON.parse(raw) : null })
|
|
55
|
+
} catch (err) {
|
|
56
|
+
fail(err)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
req.setTimeout(timeout, () => {
|
|
62
|
+
req.destroy(new Error(`timeout of ${timeout}ms exceeded`))
|
|
63
|
+
})
|
|
64
|
+
req.on('error', fail)
|
|
65
|
+
if (payload)
|
|
66
|
+
req.write(payload)
|
|
67
|
+
req.end()
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class JetcheckAPI {
|
|
72
|
+
constructor(kernel, account) {
|
|
73
|
+
this.kernel = kernel
|
|
74
|
+
this.account = account
|
|
75
|
+
this.endpoint = "/api/v1"
|
|
76
|
+
this.timeout = account.timeout || 2000
|
|
77
|
+
this.headers = {
|
|
78
|
+
'Authorization': `${account.token}`,
|
|
79
|
+
'User-Agent': `Jetcheck client v${this.kernel.version}`,
|
|
80
|
+
'Accept': 'application/json, text/plain, */*',
|
|
81
|
+
}
|
|
82
|
+
this.agent = new https.Agent({
|
|
83
|
+
rejectUnauthorized: false,
|
|
84
|
+
}).on('keylog', (line, tlsSocket) => {
|
|
85
|
+
const cert = tlsSocket.getPeerCertificate(false)
|
|
86
|
+
if (!cert || !cert.fingerprint256)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if (this.account.fingerprint !== cert.fingerprint256) {
|
|
90
|
+
console.log(`Bad fingerprint on certificate ${this.account.url} local=${this.account.fingerprint} remote=${cert.fingerprint256}`)
|
|
91
|
+
tlsSocket.destroy()
|
|
92
|
+
}
|
|
93
|
+
})
|
|
35
94
|
}
|
|
36
95
|
|
|
37
|
-
async
|
|
38
|
-
// console.log(`GET ${this.account.url}${this.endpoint}${rcs}`);
|
|
96
|
+
async call(method, rcs, data) {
|
|
39
97
|
try {
|
|
40
|
-
const response = await this.
|
|
41
|
-
|
|
98
|
+
const response = await request(method, joinURL(this.account.url, `${this.endpoint}${rcs}`), {
|
|
99
|
+
headers: this.headers,
|
|
100
|
+
body: data,
|
|
101
|
+
timeout: this.timeout,
|
|
102
|
+
agent: this.agent,
|
|
103
|
+
})
|
|
104
|
+
return { err: null, data: response.data }
|
|
42
105
|
} catch (err) {
|
|
43
|
-
return { err: err.message, data: null }
|
|
106
|
+
return { err: err.message, data: null }
|
|
44
107
|
}
|
|
45
108
|
}
|
|
46
109
|
|
|
110
|
+
async get(rcs) {
|
|
111
|
+
return this.call('GET', rcs)
|
|
112
|
+
}
|
|
113
|
+
|
|
47
114
|
async post(rcs, data) {
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const response = await this.handler.post(`${this.endpoint}${rcs}`, data);
|
|
51
|
-
return { err: null, data: response.data };
|
|
52
|
-
} catch (err) {
|
|
53
|
-
return { err: err.message, data: null };
|
|
54
|
-
}
|
|
115
|
+
return this.call('POST', rcs, data)
|
|
55
116
|
}
|
|
56
117
|
}
|
|
57
118
|
|
package/services/tests.js
CHANGED
|
@@ -90,12 +90,13 @@ module.exports = async function (kernel) {
|
|
|
90
90
|
async function pop() {
|
|
91
91
|
const task = state.current.pop()
|
|
92
92
|
if (!task) {
|
|
93
|
-
if(state.succeed.length === 0 && state.failed.length === 0) {
|
|
93
|
+
if (state.succeed.length === 0 && state.failed.length === 0) {
|
|
94
94
|
// do nothing
|
|
95
95
|
}
|
|
96
96
|
else if (state.succeed.length === 0) {
|
|
97
97
|
if (state.mode === "WORKING") {
|
|
98
98
|
console.log(`System "${testName}" is degraded`)
|
|
99
|
+
state.deadTime = new Date
|
|
99
100
|
if (test.failed) {
|
|
100
101
|
for (var item of test.failed) {
|
|
101
102
|
const module = kernel.tests.plugins[item.type]
|
|
@@ -109,6 +110,7 @@ module.exports = async function (kernel) {
|
|
|
109
110
|
else {
|
|
110
111
|
if (state.mode === "DEGRADED") {
|
|
111
112
|
console.log(`System ${testName} is restored`)
|
|
113
|
+
|
|
112
114
|
if (test.succeed) {
|
|
113
115
|
for (var item of test.succeed) {
|
|
114
116
|
const module = kernel.tests.plugins[item.type]
|
|
@@ -116,6 +118,7 @@ module.exports = async function (kernel) {
|
|
|
116
118
|
await module.trigger(item, state)
|
|
117
119
|
}
|
|
118
120
|
}
|
|
121
|
+
delete state.deadTime
|
|
119
122
|
}
|
|
120
123
|
state.mode = "WORKING"
|
|
121
124
|
}
|
|
@@ -137,7 +140,7 @@ module.exports = async function (kernel) {
|
|
|
137
140
|
state.succeed.push(task)
|
|
138
141
|
else
|
|
139
142
|
state.failed.push({ ...task, error })
|
|
140
|
-
} catch(e) {
|
|
143
|
+
} catch (e) {
|
|
141
144
|
console.log(`Test "${testName}" failed: ${e.message}`)
|
|
142
145
|
}
|
|
143
146
|
|