signalk-chiplog 2.2.0 → 2.3.1
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/CHANGELOG.md +41 -1
- package/README.md +7 -5
- package/index.js +34 -2
- package/lib/detection.js +9 -4
- package/lib/influx-history.js +66 -30
- package/lib/replay-job.js +40 -5
- package/package.json +4 -3
- package/public/js/components/ReplayView.mjs +29 -10
- package/public/js/i18n.mjs +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,44 @@ All notable changes to Chiplog are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.3.1] - 2026-09-18
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Retrospective analysis now shows what has been reconstructed so far while it is still running**, not only once it
|
|
14
|
+
finishes: passages, distance, engine/sail time, track points and events update after every committed slice instead of
|
|
15
|
+
only the clock position. A run that fails partway — an InfluxDB query timing out on a slow host — keeps that same
|
|
16
|
+
summary next to the error, instead of leaving a bare error message with no way to tell what was saved.
|
|
17
|
+
- **A retrospective replay retries an InfluxDB query that times out** up to 3 times, 5 seconds apart, instead of failing
|
|
18
|
+
the whole run on what is often just a Raspberry Pi momentarily busy sharing its InfluxDB with Signal K itself; the
|
|
19
|
+
webapp shows which attempt is under way while it waits. The timeout covers the whole round trip, including a chunk's
|
|
20
|
+
JSON still streaming in after the connection answered, not just getting a connection in the first place. Each window
|
|
21
|
+
of history is also fetched in smaller, two-hour chunks instead of six, so a slow host has less to answer per request.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **A retrospective replay running alongside live tracking** could have a live detection tick, track sample or event
|
|
26
|
+
check land on the passage the replay was reconstructing — mistaking it for the current one, since both read the same
|
|
27
|
+
`active` row — and close it early or splice live position and instrument data into a past passage. Live detection,
|
|
28
|
+
track sampling and event watching now pause for as long as a replay is running.
|
|
29
|
+
|
|
30
|
+
## [2.3.0] - 2026-09-18
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **InfluxDB query timeout** setting (`influxQueryTimeoutSeconds`, 30 s by default): how long a retrospective replay
|
|
35
|
+
waits for the InfluxDB server to answer before giving up on it as unreachable or overloaded, now configurable instead
|
|
36
|
+
of a fixed 30 seconds — useful against a Raspberry Pi that is simply slow to answer a six-hour chunk.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **Retrospective replay backfilling a gap before passages already logged live** no longer dated every reconstructed
|
|
41
|
+
passage to the most recent existing passage's end time. Detection's guard against an out-of-order departure looked at
|
|
42
|
+
the latest `end_time` in the whole logbook rather than only at passages that actually preceded the new one, so filling
|
|
43
|
+
in an earlier gap — installing Chiplog after the fact, or after a stop — pinned every reconstructed departure to that
|
|
44
|
+
unrelated, later date, and the replay summary reported no passage found for the requested period even though entries
|
|
45
|
+
had been created.
|
|
46
|
+
|
|
9
47
|
## [2.2.0] - 2026-09-18
|
|
10
48
|
|
|
11
49
|
### Added
|
|
@@ -249,7 +287,9 @@ First release.
|
|
|
249
287
|
- REST API under `/plugins/signalk-chiplog/api`, documented in [docs/API.md](docs/API.md).
|
|
250
288
|
- Single SQLite database through Node's built-in `node:sqlite`: no native module to build.
|
|
251
289
|
|
|
252
|
-
[Unreleased]: https://github.com/ricard33/signalk-chiplog/compare/v2.
|
|
290
|
+
[Unreleased]: https://github.com/ricard33/signalk-chiplog/compare/v2.3.1...HEAD
|
|
291
|
+
[2.3.1]: https://github.com/ricard33/signalk-chiplog/compare/v2.3.0...v2.3.1
|
|
292
|
+
[2.3.0]: https://github.com/ricard33/signalk-chiplog/compare/v2.2.0...v2.3.0
|
|
253
293
|
[2.2.0]: https://github.com/ricard33/signalk-chiplog/compare/v2.1.0...v2.2.0
|
|
254
294
|
[2.1.0]: https://github.com/ricard33/signalk-chiplog/compare/v2.0.0...v2.1.0
|
|
255
295
|
[2.0.0]: https://github.com/ricard33/signalk-chiplog/compare/v1.2.0...v2.0.0
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Chiplog
|
|
2
2
|
|
|
3
|
-
An automated logbook for [Signal K](https://signalk.org). Chiplog writes the logbook from the data already on
|
|
4
|
-
boat's Signal K server — passages, track, engine and sail, instrument readings, alarms — and lets the crew add what
|
|
3
|
+
An automated nautical logbook for [Signal K](https://signalk.org). Chiplog writes the logbook from the data already on
|
|
4
|
+
your boat's Signal K server — passages, track, engine and sail, instrument readings, alarms — and lets the crew add what
|
|
5
5
|
sensors cannot know from a tablet at the helm: manoeuvres, notes and handwriting.
|
|
6
6
|
|
|
7
7
|
- **One entry per passage**, opened when the boat leaves and closed as soon as it arrives, carrying on after short stops
|
|
@@ -348,6 +348,7 @@ In the Signal K admin, **Apps & Plugins → Configuration → Chiplog**.
|
|
|
348
348
|
| InfluxDB database | — | |
|
|
349
349
|
| InfluxDB username / password | — | Leave empty if the database needs none. |
|
|
350
350
|
| InfluxDB protocol | http | `http` or `https`. |
|
|
351
|
+
| InfluxDB query timeout | 30 s | Each retrospective query gives up and reports an error past this, instead of hanging against an unreachable or overloaded database. |
|
|
351
352
|
| InfluxDB vessel context | this server's own | Only needed running the replay from a different Signal K server than the one that wrote the history, e.g. development pointed at a production database. |
|
|
352
353
|
|
|
353
354
|
## Signal K data used 🔌
|
|
@@ -497,9 +498,10 @@ context** in the plugin configuration.
|
|
|
497
498
|
### A retrospective analysis takes minutes then fails with no clear reason
|
|
498
499
|
|
|
499
500
|
The InfluxDB server did not answer — unreachable, overloaded, a firewall or a VPN not connected. Each query now gives up
|
|
500
|
-
after 30 seconds with the connection problem it ran into, rather than hanging
|
|
501
|
-
failure; check that the server named in the plugin configuration is reachable
|
|
502
|
-
is not overloaded
|
|
501
|
+
after **InfluxDB query timeout** (30 seconds by default) with the connection problem it ran into, rather than hanging
|
|
502
|
+
until some far longer, less informative failure; check that the server named in the plugin configuration is reachable
|
|
503
|
+
from wherever Signal K runs, and that it is not overloaded — or raise the timeout if it is simply slow to answer a
|
|
504
|
+
six-hour chunk.
|
|
503
505
|
|
|
504
506
|
### A retrospective analysis over several days makes the InfluxDB server unresponsive
|
|
505
507
|
|
package/index.js
CHANGED
|
@@ -256,6 +256,14 @@ module.exports = function (app) {
|
|
|
256
256
|
enum: ['http', 'https'],
|
|
257
257
|
default: INFLUX_DEFAULTS.influxProtocol
|
|
258
258
|
},
|
|
259
|
+
influxQueryTimeoutSeconds: {
|
|
260
|
+
type: 'number',
|
|
261
|
+
title: 'InfluxDB query timeout (seconds)',
|
|
262
|
+
description:
|
|
263
|
+
'Each retrospective query is given up on and reported as an error past this, rather than hanging indefinitely against an unreachable or overloaded database',
|
|
264
|
+
default: INFLUX_DEFAULTS.influxQueryTimeoutSeconds,
|
|
265
|
+
minimum: 1
|
|
266
|
+
},
|
|
259
267
|
influxSelfContext: {
|
|
260
268
|
type: 'string',
|
|
261
269
|
title: 'InfluxDB vessel context',
|
|
@@ -266,6 +274,20 @@ module.exports = function (app) {
|
|
|
266
274
|
};
|
|
267
275
|
|
|
268
276
|
function runDetection() {
|
|
277
|
+
// A retrospective replay drives the same detector against a past window,
|
|
278
|
+
// holding its own passage 'active' in log_entries for as long as it takes
|
|
279
|
+
// to close it there — live detection, track sampling and event watching
|
|
280
|
+
// must not touch that row in the meantime, or a live tick would treat a
|
|
281
|
+
// reconstructed passage as the current one and close it early or splice
|
|
282
|
+
// live data into it.
|
|
283
|
+
if (replayJob?.status().running) {
|
|
284
|
+
const status = 'Retrospective replay in progress — live tracking paused';
|
|
285
|
+
if (status !== lastStatus) {
|
|
286
|
+
app.setPluginStatus(status);
|
|
287
|
+
lastStatus = status;
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
269
291
|
try {
|
|
270
292
|
const outcome = detector.tick();
|
|
271
293
|
usbExport.afterDetection(outcome);
|
|
@@ -384,6 +406,8 @@ module.exports = function (app) {
|
|
|
384
406
|
influxUsername: config.influxUsername || null,
|
|
385
407
|
influxPassword: config.influxPassword || null,
|
|
386
408
|
influxProtocol: config.influxProtocol || INFLUX_DEFAULTS.influxProtocol,
|
|
409
|
+
influxQueryTimeoutSeconds:
|
|
410
|
+
config.influxQueryTimeoutSeconds ?? INFLUX_DEFAULTS.influxQueryTimeoutSeconds,
|
|
387
411
|
influxSelfContext: config.influxSelfContext || null
|
|
388
412
|
};
|
|
389
413
|
|
|
@@ -461,11 +485,19 @@ module.exports = function (app) {
|
|
|
461
485
|
timers = [
|
|
462
486
|
setInterval(runDetection, TICK_INTERVAL_MS),
|
|
463
487
|
setInterval(
|
|
464
|
-
guarded('Track recording', () =>
|
|
488
|
+
guarded('Track recording', () => {
|
|
489
|
+
if (!replayJob?.status().running) {
|
|
490
|
+
recorder.sample();
|
|
491
|
+
}
|
|
492
|
+
}),
|
|
465
493
|
SAMPLE_INTERVAL_MS
|
|
466
494
|
),
|
|
467
495
|
setInterval(
|
|
468
|
-
guarded('Event watching', () =>
|
|
496
|
+
guarded('Event watching', () => {
|
|
497
|
+
if (!replayJob?.status().running) {
|
|
498
|
+
watcher.check();
|
|
499
|
+
}
|
|
500
|
+
}),
|
|
469
501
|
CHECK_INTERVAL_MS
|
|
470
502
|
),
|
|
471
503
|
setInterval(
|
package/lib/detection.js
CHANGED
|
@@ -314,11 +314,16 @@ function createPassageDetector({ db, readSelfPath, settings, clock = Date.now })
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
function openPassage(now, position, departure) {
|
|
317
|
+
const startIso = iso(departure.time);
|
|
318
|
+
// Only a passage that actually precedes this departure can push it later
|
|
319
|
+
// -- not just whichever entry happens to hold the latest end_time. A
|
|
320
|
+
// retrospective replay filling a gap before passages logged live (SPEC
|
|
321
|
+
// §4.10) would otherwise have every reconstructed departure clamped to
|
|
322
|
+
// the most recent (unrelated, later) passage's end.
|
|
317
323
|
const previousEnd = db
|
|
318
|
-
.prepare('SELECT MAX(end_time) AS endTime FROM log_entries')
|
|
319
|
-
.get().endTime;
|
|
320
|
-
const startTime =
|
|
321
|
-
previousEnd && iso(departure.time) < previousEnd ? previousEnd : iso(departure.time);
|
|
324
|
+
.prepare('SELECT MAX(end_time) AS endTime FROM log_entries WHERE start_time < ?')
|
|
325
|
+
.get(startIso).endTime;
|
|
326
|
+
const startTime = previousEnd && startIso < previousEnd ? previousEnd : startIso;
|
|
322
327
|
const start = departure.position ?? position;
|
|
323
328
|
const place = initialPlaceName(db, start, settings.placeMatchRadius);
|
|
324
329
|
|
package/lib/influx-history.js
CHANGED
|
@@ -12,17 +12,25 @@ const { AUTOSTATE_SOURCE_PREFIX, MAX_AGE_MS, UNDERWAY_STATES } = require('./dete
|
|
|
12
12
|
// packets, a VPN not connected) can otherwise hang far longer than this --
|
|
13
13
|
// Node's fetch has no default timeout of its own -- for an error that is no
|
|
14
14
|
// clearer once it finally arrives, so `query` bounds every request itself.
|
|
15
|
-
|
|
15
|
+
// Configurable (`influxQueryTimeoutSeconds`): a Raspberry Pi under load can
|
|
16
|
+
// need longer than the default to answer a six-hour chunk.
|
|
17
|
+
const DEFAULT_QUERY_TIMEOUT_SECONDS = 30;
|
|
16
18
|
|
|
17
19
|
// A resource-constrained host (a Raspberry Pi running both Signal K and
|
|
18
20
|
// InfluxDB) can be brought down by one query spanning weeks across every
|
|
19
|
-
// path at once;
|
|
21
|
+
// path at once; two hours at a time keeps each request's result small
|
|
20
22
|
// regardless of how long the requested range is, with a short pause between
|
|
21
23
|
// them so the database is never asked for the next one while still
|
|
22
24
|
// recovering from the last.
|
|
23
|
-
const CHUNK_MS =
|
|
25
|
+
const CHUNK_MS = 2 * 60 * 60 * 1000;
|
|
24
26
|
const CHUNK_PAUSE_MS = 200;
|
|
25
27
|
|
|
28
|
+
// A query that times out is retried rather than failing the whole replay
|
|
29
|
+
// outright -- a Raspberry Pi sharing its InfluxDB with Signal K itself is
|
|
30
|
+
// often just busy for a moment, not actually unreachable.
|
|
31
|
+
const QUERY_MAX_RETRIES = 3;
|
|
32
|
+
const QUERY_RETRY_DELAY_MS = 5 * 1000;
|
|
33
|
+
|
|
26
34
|
// The motion scan asks for one mean per minute, light enough to cover a week
|
|
27
35
|
// per request.
|
|
28
36
|
const SCAN_BUCKET_MS = 60 * 1000;
|
|
@@ -34,7 +42,8 @@ const INFLUX_DEFAULTS = {
|
|
|
34
42
|
influxPort: 8086,
|
|
35
43
|
influxDatabase: '',
|
|
36
44
|
influxUsername: '',
|
|
37
|
-
influxPassword: ''
|
|
45
|
+
influxPassword: '',
|
|
46
|
+
influxQueryTimeoutSeconds: DEFAULT_QUERY_TIMEOUT_SECONDS
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
// Everything detection, track recording and observations read live (SPEC
|
|
@@ -107,9 +116,13 @@ function createInfluxHistory({
|
|
|
107
116
|
username,
|
|
108
117
|
password,
|
|
109
118
|
selfContext,
|
|
119
|
+
queryTimeoutSeconds = INFLUX_DEFAULTS.influxQueryTimeoutSeconds,
|
|
110
120
|
fetch = globalThis.fetch,
|
|
111
|
-
signal
|
|
121
|
+
signal,
|
|
122
|
+
onRetry = () => {},
|
|
123
|
+
retryDelayMs = QUERY_RETRY_DELAY_MS
|
|
112
124
|
}) {
|
|
125
|
+
const queryTimeoutMs = queryTimeoutSeconds * 1000;
|
|
113
126
|
// path -> [{ time, node }], ascending by time; `node` is what readSelfPath
|
|
114
127
|
// answers, built once here rather than on every read.
|
|
115
128
|
const series = new Map();
|
|
@@ -131,36 +144,59 @@ function createInfluxHistory({
|
|
|
131
144
|
epoch: 'ms',
|
|
132
145
|
q: statements.join(';')
|
|
133
146
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
// One full round trip: the timeout signal covers reading the response
|
|
149
|
+
// body too, not just getting the headers back, so a database that is slow
|
|
150
|
+
// to stream a large chunk's JSON times out here just as it would waiting
|
|
151
|
+
// for the connection -- both must retry the same way.
|
|
152
|
+
async function attempt() {
|
|
153
|
+
const timeout = AbortSignal.timeout(queryTimeoutMs);
|
|
154
|
+
let response;
|
|
155
|
+
try {
|
|
156
|
+
response = await fetch(url, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers,
|
|
159
|
+
body,
|
|
160
|
+
signal: signal ? AbortSignal.any([timeout, signal]) : timeout
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (signal?.aborted || err.name === 'TimeoutError') {
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
// Node's fetch wraps a connection failure (wrong host, refused,
|
|
167
|
+
// certificate…) as a bare "fetch failed"; the actual reason is here.
|
|
148
168
|
throw new Error(
|
|
149
|
-
`InfluxDB at ${protocol}://${host}:${port}
|
|
169
|
+
`Could not reach InfluxDB at ${protocol}://${host}:${port}: ${err.cause?.message ?? err.message}`,
|
|
150
170
|
{ cause: err }
|
|
151
171
|
);
|
|
152
172
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{ cause: err }
|
|
158
|
-
);
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
throw new Error(`InfluxDB query failed: ${response.status} ${await response.text()}`);
|
|
175
|
+
}
|
|
176
|
+
return response.json();
|
|
159
177
|
}
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
|
|
179
|
+
let payload;
|
|
180
|
+
// Each attempt gets its own full timeout budget, not a shared one left
|
|
181
|
+
// over from the last -- a query that timed out once is retried outright,
|
|
182
|
+
// not with less time to answer than before.
|
|
183
|
+
for (let retries = 0; ; retries += 1) {
|
|
184
|
+
try {
|
|
185
|
+
payload = await attempt();
|
|
186
|
+
onRetry(null);
|
|
187
|
+
break;
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (signal?.aborted || err.name !== 'TimeoutError') {
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
const message = `InfluxDB at ${protocol}://${host}:${port} did not answer within ${queryTimeoutSeconds}s`;
|
|
193
|
+
if (retries >= QUERY_MAX_RETRIES) {
|
|
194
|
+
throw new Error(message, { cause: err });
|
|
195
|
+
}
|
|
196
|
+
onRetry(retries + 1, QUERY_MAX_RETRIES, message);
|
|
197
|
+
await sleep(retryDelayMs);
|
|
198
|
+
}
|
|
162
199
|
}
|
|
163
|
-
const payload = await response.json();
|
|
164
200
|
return payload.results.map((result) => {
|
|
165
201
|
if (result.error) {
|
|
166
202
|
throw new Error(`InfluxDB query error: ${result.error}`);
|
|
@@ -477,4 +513,4 @@ function createInfluxHistory({
|
|
|
477
513
|
return { preload, clear, scanMotion, readSelfPath };
|
|
478
514
|
}
|
|
479
515
|
|
|
480
|
-
module.exports = { createInfluxHistory, INFLUX_DEFAULTS };
|
|
516
|
+
module.exports = { createInfluxHistory, INFLUX_DEFAULTS, CHUNK_MS, QUERY_MAX_RETRIES };
|
package/lib/replay-job.js
CHANGED
|
@@ -13,7 +13,8 @@ function createReplayJob({
|
|
|
13
13
|
clock = Date.now,
|
|
14
14
|
log = () => {},
|
|
15
15
|
fetch = globalThis.fetch,
|
|
16
|
-
onDone = () => {}
|
|
16
|
+
onDone = () => {},
|
|
17
|
+
retryDelayMs
|
|
17
18
|
}) {
|
|
18
19
|
let current = null;
|
|
19
20
|
let lastResult = null;
|
|
@@ -71,8 +72,21 @@ function createReplayJob({
|
|
|
71
72
|
username: settings.influxUsername,
|
|
72
73
|
password: settings.influxPassword,
|
|
73
74
|
selfContext: settings.influxSelfContext || app.selfContext,
|
|
75
|
+
queryTimeoutSeconds: settings.influxQueryTimeoutSeconds,
|
|
74
76
|
fetch,
|
|
75
|
-
signal
|
|
77
|
+
signal,
|
|
78
|
+
retryDelayMs,
|
|
79
|
+
onRetry: (attempt, of, message) => {
|
|
80
|
+
if (attempt !== null) {
|
|
81
|
+
log(
|
|
82
|
+
'error',
|
|
83
|
+
`Retrospective replay: InfluxDB query timed out, retrying (attempt ${attempt}/${of}): ${message}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (current) {
|
|
87
|
+
current.retry = attempt === null ? null : { attempt, of, message };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
76
90
|
});
|
|
77
91
|
try {
|
|
78
92
|
await runWindowedReplay({
|
|
@@ -90,6 +104,11 @@ function createReplayJob({
|
|
|
90
104
|
onProgress: (nowMs) => {
|
|
91
105
|
if (current) {
|
|
92
106
|
current.now = iso(nowMs);
|
|
107
|
+
// Recomputed each slice so the webapp can show what has actually
|
|
108
|
+
// been committed so far, not just how far the clock has got --
|
|
109
|
+
// the only way to know anything was saved if the run then times
|
|
110
|
+
// out or otherwise fails.
|
|
111
|
+
current.summary = summarise(afterId, to);
|
|
93
112
|
}
|
|
94
113
|
}
|
|
95
114
|
});
|
|
@@ -104,7 +123,13 @@ function createReplayJob({
|
|
|
104
123
|
summary: summarise(afterId, to)
|
|
105
124
|
};
|
|
106
125
|
} else {
|
|
107
|
-
lastError = {
|
|
126
|
+
lastError = {
|
|
127
|
+
at: iso(clock()),
|
|
128
|
+
from,
|
|
129
|
+
to,
|
|
130
|
+
message: err.message,
|
|
131
|
+
summary: summarise(afterId, to)
|
|
132
|
+
};
|
|
108
133
|
log('error', `Retrospective replay failed (${from} to ${to}): ${err.message}`);
|
|
109
134
|
}
|
|
110
135
|
} finally {
|
|
@@ -140,7 +165,15 @@ function createReplayJob({
|
|
|
140
165
|
);
|
|
141
166
|
}
|
|
142
167
|
const controller = new AbortController();
|
|
143
|
-
current = {
|
|
168
|
+
current = {
|
|
169
|
+
from,
|
|
170
|
+
to,
|
|
171
|
+
startedAt: iso(clock()),
|
|
172
|
+
now: from,
|
|
173
|
+
phase: 'scanning',
|
|
174
|
+
retry: null,
|
|
175
|
+
controller
|
|
176
|
+
};
|
|
144
177
|
lastError = null;
|
|
145
178
|
perform(from, to, controller.signal);
|
|
146
179
|
return { from, to };
|
|
@@ -162,7 +195,9 @@ function createReplayJob({
|
|
|
162
195
|
from: current.from,
|
|
163
196
|
to: current.to,
|
|
164
197
|
now: current.now,
|
|
165
|
-
phase: current.phase
|
|
198
|
+
phase: current.phase,
|
|
199
|
+
summary: current.summary ?? null,
|
|
200
|
+
retry: current.retry
|
|
166
201
|
},
|
|
167
202
|
lastResult,
|
|
168
203
|
lastError
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-chiplog",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "Nautical logbook for Signal K: automatic entries, handwritten notes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"index.js",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"./docs/screenshots/04-entry-app.png"
|
|
37
37
|
],
|
|
38
38
|
"recommends": [
|
|
39
|
-
"signalk-to-influxdb"
|
|
39
|
+
"signalk-to-influxdb",
|
|
40
|
+
"signalk-autostate"
|
|
40
41
|
]
|
|
41
42
|
},
|
|
42
43
|
"author": "Cedric RICARD <ricard33@gmail.com>",
|
|
@@ -26,19 +26,35 @@ function Progress({ progress }) {
|
|
|
26
26
|
time: `${format.shortDate(progress.now)} ${format.time(progress.now)}`
|
|
27
27
|
})}
|
|
28
28
|
</p>
|
|
29
|
+
${
|
|
30
|
+
progress.retry &&
|
|
31
|
+
html`<p class="notice">
|
|
32
|
+
${t('replay.retrying', {
|
|
33
|
+
attempt: progress.retry.attempt,
|
|
34
|
+
of: progress.retry.of,
|
|
35
|
+
message: progress.retry.message
|
|
36
|
+
})}
|
|
37
|
+
</p>`
|
|
38
|
+
}
|
|
39
|
+
${
|
|
40
|
+
progress.phase === 'replaying' &&
|
|
41
|
+
html`<${Summary} summary=${progress.summary} live=${true} />`
|
|
42
|
+
}
|
|
29
43
|
</div>
|
|
30
44
|
`;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
// What
|
|
34
|
-
//
|
|
35
|
-
|
|
47
|
+
// What a run added, live while it is still going (so a timeout partway
|
|
48
|
+
// through still leaves something to show for it) and again for the final
|
|
49
|
+
// outcome -- also after a cancellation, since what was reconstructed up to
|
|
50
|
+
// that point stays on record either way.
|
|
51
|
+
function Summary({ summary, live = false }) {
|
|
36
52
|
const { t, format } = useLocale();
|
|
37
53
|
if (!summary) {
|
|
38
54
|
return null;
|
|
39
55
|
}
|
|
40
56
|
if (summary.passages === 0) {
|
|
41
|
-
return html`<p class="muted">${t('replay.summaryEmpty')}</p>`;
|
|
57
|
+
return live ? null : html`<p class="muted">${t('replay.summaryEmpty')}</p>`;
|
|
42
58
|
}
|
|
43
59
|
const facts = [
|
|
44
60
|
['replay.summaryPassages', format.count(summary.passages)],
|
|
@@ -66,12 +82,15 @@ function Outcome({ status }) {
|
|
|
66
82
|
const { t, format } = useLocale();
|
|
67
83
|
if (status.lastError) {
|
|
68
84
|
const { lastError } = status;
|
|
69
|
-
return html
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
return html`
|
|
86
|
+
<p class="notice notice-error">
|
|
87
|
+
${t('replay.failed', {
|
|
88
|
+
time: format.time(lastError.at),
|
|
89
|
+
message: lastError.message
|
|
90
|
+
})}
|
|
91
|
+
</p>
|
|
92
|
+
<${Summary} summary=${lastError.summary} />
|
|
93
|
+
`;
|
|
75
94
|
}
|
|
76
95
|
if (status.lastResult) {
|
|
77
96
|
const { lastResult } = status;
|
package/public/js/i18n.mjs
CHANGED
|
@@ -351,6 +351,7 @@ export const MESSAGES = {
|
|
|
351
351
|
'replay.start': 'Reconstruct',
|
|
352
352
|
'replay.cancel': 'Cancel',
|
|
353
353
|
'replay.progress': '{percent}% — at {time}',
|
|
354
|
+
'replay.retrying': 'Retrying after a timeout (attempt {attempt}/{of}): {message}',
|
|
354
355
|
'replay.done': 'Reconstruction finished at {time}.',
|
|
355
356
|
'replay.cancelled': 'Reconstruction cancelled at {time}.',
|
|
356
357
|
'replay.failed': 'Reconstruction failed at {time}: {message}',
|
|
@@ -802,6 +803,8 @@ export const MESSAGES = {
|
|
|
802
803
|
'replay.start': 'Reconstruire',
|
|
803
804
|
'replay.cancel': 'Annuler',
|
|
804
805
|
'replay.progress': '{percent} % — à {time}',
|
|
806
|
+
'replay.retrying':
|
|
807
|
+
'Nouvelle tentative après un délai dépassé (essai {attempt}/{of}) : {message}',
|
|
805
808
|
'replay.done': 'Reconstruction terminée à {time}.',
|
|
806
809
|
'replay.cancelled': 'Reconstruction annulée à {time}.',
|
|
807
810
|
'replay.failed': 'Échec de la reconstruction à {time} : {message}',
|