signalk-automation-plugin 0.0.1 → 0.0.2
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 +16 -8
- package/examples/automations.yaml +49 -44
- package/lib/engine.js +233 -20
- package/lib/geo.js +17 -9
- package/lib/git.js +33 -1
- package/lib/runtime.js +215 -21
- package/lib/store.js +42 -1
- package/lib/yaml.js +85 -3
- package/package.json +2 -1
- package/plugin/index.js +205 -52
- package/public/app.js +493 -72
- package/public/icon.png +0 -0
- package/public/index.html +48 -21
- package/public/style.css +195 -20
package/plugin/index.js
CHANGED
|
@@ -14,8 +14,14 @@ function sendJson (res, body, status) {
|
|
|
14
14
|
|
|
15
15
|
function readJson (req) {
|
|
16
16
|
return new Promise((resolve, reject) => {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const parsed = req.body
|
|
18
|
+
const hasParsed =
|
|
19
|
+
parsed &&
|
|
20
|
+
typeof parsed === 'object' &&
|
|
21
|
+
!Buffer.isBuffer(parsed) &&
|
|
22
|
+
Object.keys(parsed).length > 0
|
|
23
|
+
if (hasParsed || req.readableEnded) {
|
|
24
|
+
resolve(hasParsed ? parsed : (parsed && typeof parsed === 'object' && !Buffer.isBuffer(parsed) ? parsed : {}))
|
|
19
25
|
return
|
|
20
26
|
}
|
|
21
27
|
let raw = ''
|
|
@@ -27,7 +33,7 @@ function readJson (req) {
|
|
|
27
33
|
})
|
|
28
34
|
req.on('end', () => {
|
|
29
35
|
if (!raw) {
|
|
30
|
-
resolve({})
|
|
36
|
+
resolve(parsed && typeof parsed === 'object' && !Buffer.isBuffer(parsed) ? parsed : {})
|
|
31
37
|
return
|
|
32
38
|
}
|
|
33
39
|
try {
|
|
@@ -88,6 +94,8 @@ module.exports = function (app) {
|
|
|
88
94
|
started: false,
|
|
89
95
|
automations: [],
|
|
90
96
|
helpers: [],
|
|
97
|
+
zones: [],
|
|
98
|
+
switches: [],
|
|
91
99
|
errors: [],
|
|
92
100
|
yaml: '',
|
|
93
101
|
commits: []
|
|
@@ -110,6 +118,19 @@ module.exports = function (app) {
|
|
|
110
118
|
try {
|
|
111
119
|
const body = await readJson(req)
|
|
112
120
|
const snap = rt.activateCommit(body.sha)
|
|
121
|
+
rewireAfterYamlChange()
|
|
122
|
+
sendJson(res, snap)
|
|
123
|
+
} catch (err) {
|
|
124
|
+
sendJson(res, { error: err.message }, 400)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function handleReload (req, res) {
|
|
129
|
+
const rt = requireRuntime(res)
|
|
130
|
+
if (!rt) return
|
|
131
|
+
try {
|
|
132
|
+
const snap = rt.reloadWorkingTree()
|
|
133
|
+
rewireAfterYamlChange()
|
|
113
134
|
sendJson(res, snap)
|
|
114
135
|
} catch (err) {
|
|
115
136
|
sendJson(res, { error: err.message }, 400)
|
|
@@ -120,9 +141,37 @@ module.exports = function (app) {
|
|
|
120
141
|
const rt = requireRuntime(res)
|
|
121
142
|
if (!rt) return
|
|
122
143
|
try {
|
|
144
|
+
const auto = rt.doc.automations.find((a) => a.id === id)
|
|
145
|
+
if (!auto) {
|
|
146
|
+
sendJson(res, { error: 'Unknown automation: ' + id }, 404)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
123
149
|
const body = await readJson(req)
|
|
150
|
+
if (!Object.prototype.hasOwnProperty.call(body, 'enabled')) {
|
|
151
|
+
throw new Error('enabled is required')
|
|
152
|
+
}
|
|
124
153
|
rt.setEnabled(id, body.enabled)
|
|
125
|
-
sendJson(res, { id, enabled: rt.isEnabled(
|
|
154
|
+
sendJson(res, { id, enabled: rt.isEnabled(auto) })
|
|
155
|
+
} catch (err) {
|
|
156
|
+
sendJson(res, { error: err.message }, 400)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function handleVerbose (req, res, id) {
|
|
161
|
+
const rt = requireRuntime(res)
|
|
162
|
+
if (!rt) return
|
|
163
|
+
try {
|
|
164
|
+
const auto = rt.doc.automations.find((a) => a.id === id)
|
|
165
|
+
if (!auto) {
|
|
166
|
+
sendJson(res, { error: 'Unknown automation: ' + id }, 404)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
const body = await readJson(req)
|
|
170
|
+
if (!Object.prototype.hasOwnProperty.call(body, 'verbose')) {
|
|
171
|
+
throw new Error('verbose is required')
|
|
172
|
+
}
|
|
173
|
+
rt.setVerbose(id, body.verbose)
|
|
174
|
+
sendJson(res, { id, verbose: rt.isVerbose(auto) })
|
|
126
175
|
} catch (err) {
|
|
127
176
|
sendJson(res, { error: err.message }, 400)
|
|
128
177
|
}
|
|
@@ -133,7 +182,7 @@ module.exports = function (app) {
|
|
|
133
182
|
if (!rt) return
|
|
134
183
|
try {
|
|
135
184
|
const body = await readJson(req)
|
|
136
|
-
const value = rt.setHelper(id, body.value)
|
|
185
|
+
const value = await rt.setHelper(id, body.value)
|
|
137
186
|
emitHelper(id, value)
|
|
138
187
|
sendJson(res, { id, value })
|
|
139
188
|
} catch (err) {
|
|
@@ -141,11 +190,11 @@ module.exports = function (app) {
|
|
|
141
190
|
}
|
|
142
191
|
}
|
|
143
192
|
|
|
144
|
-
function handleHelperReset (req, res, id) {
|
|
193
|
+
async function handleHelperReset (req, res, id) {
|
|
145
194
|
const rt = requireRuntime(res)
|
|
146
195
|
if (!rt) return
|
|
147
196
|
try {
|
|
148
|
-
const value = rt.resetHelper(id)
|
|
197
|
+
const value = await rt.resetHelper(id)
|
|
149
198
|
emitHelper(id, value)
|
|
150
199
|
sendJson(res, { id, value: value == null ? null : value })
|
|
151
200
|
} catch (err) {
|
|
@@ -153,25 +202,55 @@ module.exports = function (app) {
|
|
|
153
202
|
}
|
|
154
203
|
}
|
|
155
204
|
|
|
205
|
+
async function handleRun (req, res, id) {
|
|
206
|
+
const rt = requireRuntime(res)
|
|
207
|
+
if (!rt) return
|
|
208
|
+
try {
|
|
209
|
+
const record = await rt.runNow(id)
|
|
210
|
+
sendJson(res, record)
|
|
211
|
+
} catch (err) {
|
|
212
|
+
sendJson(res, { error: err.message }, 404)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
156
216
|
plugin.registerWithRouter = function (router) {
|
|
217
|
+
const write = typeof router.access === 'function' ? router.access('readwrite') : router
|
|
157
218
|
router.get('/status', (req, res) => sendJson(res, snapshot()))
|
|
158
219
|
router.get('/yaml', (req, res) => sendJson(res, { yaml: snapshot().yaml, sha: snapshot().activeSha }))
|
|
159
220
|
router.get('/commits', (req, res) => sendJson(res, { commits: snapshot().commits, activeSha: snapshot().activeSha }))
|
|
221
|
+
router.get('/diff', (req, res) => {
|
|
222
|
+
const rt = requireRuntime(res)
|
|
223
|
+
if (!rt) return
|
|
224
|
+
try {
|
|
225
|
+
sendJson(res, rt.diffCommit(queryParam(req, 'sha')))
|
|
226
|
+
} catch (err) {
|
|
227
|
+
sendJson(res, { error: err.message }, 400)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
160
230
|
router.get('/automations/:id/traces', (req, res) => {
|
|
161
231
|
const rt = requireRuntime(res)
|
|
162
232
|
if (!rt) return
|
|
163
233
|
sendJson(res, { id: req.params.id, traces: rt.traces(req.params.id) })
|
|
164
234
|
})
|
|
165
|
-
|
|
235
|
+
write.post('/live', (req, res) => {
|
|
166
236
|
handleLive(req, res)
|
|
167
237
|
})
|
|
168
|
-
|
|
238
|
+
write.post('/reload', (req, res) => {
|
|
239
|
+
handleReload(req, res)
|
|
240
|
+
})
|
|
241
|
+
write.post('/automations/:id/enabled', (req, res) => {
|
|
169
242
|
handleEnabled(req, res, req.params.id)
|
|
170
243
|
})
|
|
171
|
-
|
|
244
|
+
write.post('/automations/:id/verbose', (req, res) => {
|
|
245
|
+
handleVerbose(req, res, req.params.id)
|
|
246
|
+
})
|
|
247
|
+
write.post('/automations/:id/run', (req, res) => {
|
|
248
|
+
handleRun(req, res, req.params.id)
|
|
249
|
+
})
|
|
250
|
+
write.put('/helpers/:id', (req, res) => {
|
|
172
251
|
handleHelperPut(req, res, req.params.id)
|
|
173
252
|
})
|
|
174
|
-
|
|
253
|
+
write.post('/helpers/:id/reset', (req, res) => {
|
|
175
254
|
handleHelperReset(req, res, req.params.id)
|
|
176
255
|
})
|
|
177
256
|
}
|
|
@@ -185,6 +264,17 @@ module.exports = function (app) {
|
|
|
185
264
|
router.get(prefix + '/commits', (req, res) =>
|
|
186
265
|
sendJson(res, { commits: snapshot().commits, activeSha: snapshot().activeSha })
|
|
187
266
|
)
|
|
267
|
+
router.get(prefix + '/diff', (req, res) => {
|
|
268
|
+
if (!runtime) {
|
|
269
|
+
sendJson(res, { error: 'plugin not started' }, 409)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
sendJson(res, runtime.diffCommit(queryParam(req, 'sha')))
|
|
274
|
+
} catch (err) {
|
|
275
|
+
sendJson(res, { error: err.message }, 400)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
188
278
|
router.get(prefix + '/automations/:id/traces', (req, res) => {
|
|
189
279
|
if (!runtime) {
|
|
190
280
|
sendJson(res, { error: 'plugin not started' }, 409)
|
|
@@ -217,14 +307,38 @@ module.exports = function (app) {
|
|
|
217
307
|
|
|
218
308
|
function putPath (p, value) {
|
|
219
309
|
return new Promise((resolve, reject) => {
|
|
220
|
-
if (typeof app.putSelfPath
|
|
221
|
-
app.
|
|
310
|
+
if (typeof app.putSelfPath !== 'function') {
|
|
311
|
+
app.handleMessage(PLUGIN_ID, {
|
|
312
|
+
updates: [{ values: [{ path: p, value }] }]
|
|
313
|
+
})
|
|
314
|
+
resolve()
|
|
222
315
|
return
|
|
223
316
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
317
|
+
let settled = false
|
|
318
|
+
const finish = (err) => {
|
|
319
|
+
if (settled) return
|
|
320
|
+
settled = true
|
|
321
|
+
if (err) reject(err)
|
|
322
|
+
else resolve()
|
|
323
|
+
}
|
|
324
|
+
const fromReply = (reply) => {
|
|
325
|
+
if (reply == null) return finish(null)
|
|
326
|
+
if (reply instanceof Error) return finish(reply)
|
|
327
|
+
const code = reply.statusCode
|
|
328
|
+
if (typeof code === 'number' && code >= 400) {
|
|
329
|
+
finish(new Error(reply.message || ('PUT failed (' + code + ') for ' + p)))
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
finish(null)
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const ret = app.putSelfPath(p, value, fromReply)
|
|
336
|
+
if (ret && typeof ret.then === 'function') {
|
|
337
|
+
ret.then(fromReply, finish)
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
finish(err)
|
|
341
|
+
}
|
|
228
342
|
})
|
|
229
343
|
}
|
|
230
344
|
|
|
@@ -265,57 +379,90 @@ module.exports = function (app) {
|
|
|
265
379
|
scriptTimeoutSeconds: options.scriptTimeoutSeconds,
|
|
266
380
|
put: putPath,
|
|
267
381
|
notify,
|
|
268
|
-
log: {
|
|
382
|
+
log: {
|
|
383
|
+
debug: app.debug ? app.debug.bind(app) : () => {},
|
|
384
|
+
error: app.error ? app.error.bind(app) : console.error,
|
|
385
|
+
info: (msg) => {
|
|
386
|
+
if (app.debug) app.debug(msg)
|
|
387
|
+
console.log('[signalk-automation-plugin] ' + msg)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
269
390
|
})
|
|
270
391
|
runtime.load()
|
|
392
|
+
runtime.seedFromSelf()
|
|
393
|
+
rewireAfterYamlChange()
|
|
394
|
+
runtime.evaluateOnStart().catch((err) => app.error && app.error(err))
|
|
395
|
+
|
|
396
|
+
timers.push(setInterval(() => {
|
|
397
|
+
runtime.tickSchedule().catch((err) => app.error && app.error(err))
|
|
398
|
+
}, 15000))
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function clearSubscriptions () {
|
|
402
|
+
unsubscribes.splice(0).forEach((f) => {
|
|
403
|
+
try {
|
|
404
|
+
f()
|
|
405
|
+
} catch (_) {}
|
|
406
|
+
})
|
|
407
|
+
}
|
|
271
408
|
|
|
409
|
+
function emitAllHelpers () {
|
|
410
|
+
if (!runtime) return
|
|
272
411
|
for (const [id, value] of Object.entries(runtime.helperValues)) {
|
|
273
412
|
emitHelper(id, value)
|
|
274
413
|
if (typeof app.registerPutHandler === 'function') {
|
|
275
414
|
app.registerPutHandler('vessels.self', helpers.helperPath(id), (context, p, v, cb) => {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
415
|
+
Promise.resolve(runtime.setHelper(id, v))
|
|
416
|
+
.then((next) => {
|
|
417
|
+
emitHelper(id, next)
|
|
418
|
+
if (cb) cb({ state: 'COMPLETED' })
|
|
419
|
+
})
|
|
420
|
+
.catch((err) => {
|
|
421
|
+
if (cb) cb({ state: 'COMPLETED', statusCode: 400, message: err.message })
|
|
422
|
+
})
|
|
423
|
+
return { state: 'PENDING' }
|
|
283
424
|
})
|
|
284
425
|
}
|
|
285
426
|
}
|
|
427
|
+
}
|
|
286
428
|
|
|
429
|
+
function subscribePaths () {
|
|
430
|
+
if (!runtime || !app.subscriptionmanager) return
|
|
287
431
|
const paths = require('../lib/engine').collectPaths(runtime.doc)
|
|
288
|
-
if (paths.length
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
})
|
|
432
|
+
if (!paths.length) return
|
|
433
|
+
app.subscriptionmanager.subscribe(
|
|
434
|
+
{
|
|
435
|
+
context: 'vessels.self',
|
|
436
|
+
subscribe: paths.map((p) => ({ path: p }))
|
|
437
|
+
},
|
|
438
|
+
unsubscribes,
|
|
439
|
+
(err) => app.error && app.error(err),
|
|
440
|
+
(delta) => {
|
|
441
|
+
const src = delta && delta.updates && delta.updates[0] && delta.updates[0].$source
|
|
442
|
+
;(delta.updates || []).forEach((u) => {
|
|
443
|
+
const source = u.$source || src
|
|
444
|
+
;(u.values || []).forEach((v) => {
|
|
445
|
+
runtime.handlePathChange(v.path, v.value, source).catch((err) => {
|
|
446
|
+
if (app.error) app.error(err)
|
|
304
447
|
})
|
|
305
448
|
})
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
}
|
|
309
453
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
454
|
+
function setStatusFromRuntime () {
|
|
455
|
+
if (!app.setPluginStatus || !runtime) return
|
|
456
|
+
const n = runtime.doc.automations.length
|
|
457
|
+
const err = runtime.doc.errors.length
|
|
458
|
+
app.setPluginStatus(n + ' automations' + (err ? ', ' + err + ' YAML error(s)' : ''))
|
|
459
|
+
}
|
|
313
460
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
461
|
+
function rewireAfterYamlChange () {
|
|
462
|
+
clearSubscriptions()
|
|
463
|
+
emitAllHelpers()
|
|
464
|
+
subscribePaths()
|
|
465
|
+
setStatusFromRuntime()
|
|
319
466
|
}
|
|
320
467
|
|
|
321
468
|
plugin.stop = function () {
|
|
@@ -336,5 +483,11 @@ function fsMkdir (dir) {
|
|
|
336
483
|
fs.mkdirSync(dir, { recursive: true })
|
|
337
484
|
}
|
|
338
485
|
|
|
486
|
+
function queryParam (req, name) {
|
|
487
|
+
if (req.query && req.query[name] != null) return String(req.query[name])
|
|
488
|
+
const raw = String(req.url || '').split('?')[1] || ''
|
|
489
|
+
return new URLSearchParams(raw).get(name) || ''
|
|
490
|
+
}
|
|
491
|
+
|
|
339
492
|
module.exports.app = 'app'
|
|
340
493
|
module.exports.options = 'options'
|