@mxtommy/kip 4.6.0-beta.2 → 4.7.0-beta.10
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 +12 -3
- package/package.json +17 -17
- package/plugin/history-series.service.js +125 -17
- package/plugin/index.js +168 -54
- package/plugin/kip-series-contract.js +14 -0
- package/plugin/openApi.json +90 -2
- package/plugin/sqlite-history-storage.service.js +14 -20
- package/public/3rdpartylicenses.txt +36 -0
- package/public/assets/help-docs/dashboards.md +3 -3
- package/public/assets/help-docs/datainspector.md +3 -3
- package/public/assets/help-docs/history-api.md +31 -21
- package/public/assets/help-docs/menu.json +1 -1
- package/public/assets/help-docs/nodered-control-flows.md +4 -4
- package/public/assets/help-docs/putcontrols.md +6 -6
- package/public/assets/help-docs/widget-historical-series.md +56 -12
- package/public/assets/help-docs/zones.md +1 -1
- package/public/assets/svg/icons.svg +17 -0
- package/public/{chunk-P7JKENHI.js → chunk-2GOHQZH5.js} +4 -4
- package/public/{chunk-NFJ4RQSE.js → chunk-4YDVZHMH.js} +4 -4
- package/public/{chunk-FZSLNGBK.js → chunk-6U74K6G4.js} +7 -7
- package/public/{chunk-TVNXBPFF.js → chunk-AQROQY2F.js} +1 -1
- package/public/{chunk-XBSU7OGT.js → chunk-AZC2WKQI.js} +1 -1
- package/public/{chunk-WH5CIUSB.js → chunk-BGGO4PGD.js} +1 -1
- package/public/{chunk-R36UY4Q4.js → chunk-BQPPRM7O.js} +1 -1
- package/public/{chunk-BEQKBGLG.js → chunk-BTVGQ4ZG.js} +2 -2
- package/public/{chunk-RCYOZLZB.js → chunk-CSIELI2Z.js} +2 -2
- package/public/{chunk-VXCYPAWR.js → chunk-FYDLTNP4.js} +1 -1
- package/public/{chunk-YI3MZWRZ.js → chunk-HSKVTFFQ.js} +1 -1
- package/public/{chunk-TBNKOU7M.js → chunk-IENESD5Q.js} +1 -1
- package/public/chunk-LS6AJ3JI.js +50 -0
- package/public/{chunk-SJFJEOSG.js → chunk-M37BLWHF.js} +5 -5
- package/public/chunk-MXUEYEZU.js +5 -0
- package/public/{chunk-WQSJFJLW.js → chunk-POMIQBAL.js} +2 -2
- package/public/{chunk-P4CRTB7N.js → chunk-PTLDR7X7.js} +1 -1
- package/public/{chunk-OPTBDYBL.js → chunk-PUPM3HUQ.js} +1 -1
- package/public/chunk-PZ6I6W3H.js +16 -0
- package/public/{chunk-Q2ANAJAD.js → chunk-SUWMN3AE.js} +1 -1
- package/public/{chunk-FZFDGAQO.js → chunk-WJFXI5PQ.js} +1 -1
- package/public/{chunk-BJEHRCYP.js → chunk-X44BRNVL.js} +1 -1
- package/public/{chunk-KWTS7JF7.js → chunk-Y6JCNR3H.js} +1 -1
- package/public/{chunk-VPF5756E.js → chunk-YY4ZUJFI.js} +5 -5
- package/public/{chunk-J6EEFXKZ.js → chunk-Z4K5KE3I.js} +8 -8
- package/public/index.html +1 -1
- package/public/{main-TZOV3JCT.js → main-775NFBN3.js} +1 -1
- package/public/chunk-67V4XHCY.js +0 -5
- package/public/chunk-BTFZS2TW.js +0 -16
- package/public/chunk-RFNZ4AQG.js +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
# v4.
|
|
1
|
+
# v4.6.0
|
|
2
|
+
## Improvements
|
|
3
|
+
* Built-in Time-Series storage and History-API provider now use the native node:sqlite feature, eliminating binary and external dependencies.
|
|
4
|
+
* Requires Node.js 22.5.0 or newer. If you use an older Node.js version, you must select an alternative History-API provider.
|
|
5
|
+
* **IMPORTANT:** Before upgrading Node.js, always confirm your Signal K server and OS supports the required Node.js version. See the [Signal K installation documentation](https://demo.signalk.org/documentation/Installation.html).
|
|
6
|
+
## Fixes
|
|
7
|
+
* Extending v4.5.x features to VenusOS (32bit OS) - Error: Failed to start: Error loading duckdb native binding: unsupported arch 'arm' for platform 'linux'. Fixes #979
|
|
8
|
+
* Uninstallation does not remove all files. Fixes #981
|
|
9
|
+
* Reduce unwarranted installation size.
|
|
10
|
+
# v4.5.2 - Deprecated version due to lack of VenusOS (32bit) DuckDB binary support
|
|
2
11
|
## Fixes
|
|
3
12
|
* DuckDB initialized when features are not enabled.
|
|
4
13
|
* Parquet data compression and pruning not executing.
|
|
5
|
-
# v4.5.1
|
|
14
|
+
# v4.5.1 - Deprecated version due to lack of VenusOS (32bit) DuckDB binary support
|
|
6
15
|
## Fixes
|
|
7
16
|
* DuckDB dependency causing build and installation errors. Fixes #979
|
|
8
17
|
* Reduced installation size. Fixes #980
|
|
9
|
-
# v4.5.0
|
|
18
|
+
# v4.5.0 - Deprecated version due to lack of VenusOS (32bit) DuckDB binary support
|
|
10
19
|
## New Features
|
|
11
20
|
* Effortlessly review your vessel’s history with the new Widget Historical Charts—automatically track, store, and visualize key data. Instantly access up to the last full day of performance: just two-finger tap or right-click any widget to open a seamless history dialog—no setup, no clutter, just the trends you need. (Requires Signal K v2.22.1)
|
|
12
21
|
* Dashboards now start with fully populated Data Charts, powered by KIP’s managed Time-Series History-API provider or other compatible history providers. (Requires Signal K v2.22.1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxtommy/kip",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0-beta.10",
|
|
4
4
|
"description": "An advanced and versatile marine instrumentation package to display Signal K data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -66,22 +66,22 @@
|
|
|
66
66
|
},
|
|
67
67
|
"schematics": "tools/schematics/collection.json",
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@angular/cdk": "21.2.
|
|
70
|
-
"@angular/common": "21.2.
|
|
71
|
-
"@angular/compiler": "21.2.
|
|
72
|
-
"@angular/core": "21.2.
|
|
73
|
-
"@angular/forms": "21.2.
|
|
74
|
-
"@angular/material": "21.2.
|
|
75
|
-
"@angular/animations": "21.2.
|
|
76
|
-
"@angular/platform-browser": "21.2.
|
|
77
|
-
"@angular/platform-browser-dynamic": "21.2.
|
|
78
|
-
"@angular/router": "21.2.
|
|
79
|
-
"@angular-devkit/build-angular": "^21.1
|
|
69
|
+
"@angular/cdk": "21.2.1",
|
|
70
|
+
"@angular/common": "21.2.1",
|
|
71
|
+
"@angular/compiler": "21.2.1",
|
|
72
|
+
"@angular/core": "21.2.1",
|
|
73
|
+
"@angular/forms": "21.2.1",
|
|
74
|
+
"@angular/material": "21.2.1",
|
|
75
|
+
"@angular/animations": "21.2.1",
|
|
76
|
+
"@angular/platform-browser": "21.2.1",
|
|
77
|
+
"@angular/platform-browser-dynamic": "21.2.1",
|
|
78
|
+
"@angular/router": "21.2.1",
|
|
79
|
+
"@angular-devkit/build-angular": "^21.2.1",
|
|
80
80
|
"@angular-devkit/schematics-cli": "^20.1.6",
|
|
81
|
-
"@angular/build": "^21.1
|
|
82
|
-
"@angular/cli": "^21.1
|
|
83
|
-
"@angular/compiler-cli": "21.2.
|
|
84
|
-
"@angular/language-service": "21.2.
|
|
81
|
+
"@angular/build": "^21.2.1",
|
|
82
|
+
"@angular/cli": "^21.2.1",
|
|
83
|
+
"@angular/compiler-cli": "21.2.1",
|
|
84
|
+
"@angular/language-service": "21.2.1",
|
|
85
85
|
"@types/canvas-gauges": "^2.1.8",
|
|
86
86
|
"@types/d3": "^7.4.3",
|
|
87
87
|
"@types/jasmine": "~3.6.0",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@types/js-quantities": "^1.6.6",
|
|
90
90
|
"@types/lodash-es": "^4.17.9",
|
|
91
91
|
"@types/node": "^24.1.0",
|
|
92
|
-
"angular-eslint": "21.
|
|
92
|
+
"angular-eslint": "21.3.0",
|
|
93
93
|
"codelyzer": "^6.0.0",
|
|
94
94
|
"eslint": "^9.29.0",
|
|
95
95
|
"jasmine-core": "~4.0.1",
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HistorySeriesService = void 0;
|
|
3
|
+
exports.HistorySeriesService = exports.isKipTemplateSeriesDefinition = exports.isKipSeriesEnabled = exports.isKipConcreteSeriesDefinition = void 0;
|
|
4
|
+
const kip_series_contract_1 = require("./kip-series-contract");
|
|
5
|
+
Object.defineProperty(exports, "isKipConcreteSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipConcreteSeriesDefinition; } });
|
|
6
|
+
Object.defineProperty(exports, "isKipSeriesEnabled", { enumerable: true, get: function () { return kip_series_contract_1.isKipSeriesEnabled; } });
|
|
7
|
+
Object.defineProperty(exports, "isKipTemplateSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipTemplateSeriesDefinition; } });
|
|
4
8
|
/**
|
|
5
9
|
* Manages history capture series definitions and serves History API-compatible query results.
|
|
6
10
|
*/
|
|
7
11
|
class HistorySeriesService {
|
|
8
12
|
nowProvider;
|
|
13
|
+
selfContext;
|
|
9
14
|
seriesById = new Map();
|
|
15
|
+
enabledSeriesKeysByPath = new Map();
|
|
10
16
|
lastAcceptedTimestampBySeriesKey = new Map();
|
|
11
17
|
sampleSink = null;
|
|
12
|
-
constructor(nowProvider = () => Date.now()) {
|
|
18
|
+
constructor(nowProvider = () => Date.now(), selfContext = null) {
|
|
13
19
|
this.nowProvider = nowProvider;
|
|
20
|
+
this.selfContext = selfContext;
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Returns all configured series sorted by `seriesId`.
|
|
@@ -56,6 +63,7 @@ class HistorySeriesService {
|
|
|
56
63
|
const normalized = this.normalizeSeries(input);
|
|
57
64
|
const key = normalized.seriesId;
|
|
58
65
|
this.seriesById.set(key, normalized);
|
|
66
|
+
this.rebuildEnabledPathIndex();
|
|
59
67
|
return normalized;
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
@@ -90,6 +98,7 @@ class HistorySeriesService {
|
|
|
90
98
|
this.seriesById.delete(key);
|
|
91
99
|
this.lastAcceptedTimestampBySeriesKey.delete(key);
|
|
92
100
|
});
|
|
101
|
+
this.rebuildEnabledPathIndex();
|
|
93
102
|
return true;
|
|
94
103
|
}
|
|
95
104
|
/**
|
|
@@ -103,7 +112,7 @@ class HistorySeriesService {
|
|
|
103
112
|
* console.log(result.created, result.deleted);
|
|
104
113
|
*/
|
|
105
114
|
reconcileSeries(desiredSeries) {
|
|
106
|
-
const now =
|
|
115
|
+
const now = this.nowProvider();
|
|
107
116
|
const desiredById = new Map();
|
|
108
117
|
desiredSeries.forEach(entry => {
|
|
109
118
|
const normalized = this.normalizeSeries(entry);
|
|
@@ -122,7 +131,7 @@ class HistorySeriesService {
|
|
|
122
131
|
created += 1;
|
|
123
132
|
return;
|
|
124
133
|
}
|
|
125
|
-
if (
|
|
134
|
+
if (!this.areSeriesEquivalent(existing, desired)) {
|
|
126
135
|
this.seriesById.set(seriesKey, desiredWithReconcile);
|
|
127
136
|
updated += 1;
|
|
128
137
|
}
|
|
@@ -138,6 +147,7 @@ class HistorySeriesService {
|
|
|
138
147
|
deleted += 1;
|
|
139
148
|
}
|
|
140
149
|
});
|
|
150
|
+
this.rebuildEnabledPathIndex();
|
|
141
151
|
return {
|
|
142
152
|
created,
|
|
143
153
|
updated,
|
|
@@ -157,12 +167,10 @@ class HistorySeriesService {
|
|
|
157
167
|
* service.recordSample('abc', 12.4, Date.now());
|
|
158
168
|
*/
|
|
159
169
|
recordSample(seriesId, value, timestamp) {
|
|
160
|
-
|
|
161
|
-
.find(([, series]) => series.seriesId === seriesId);
|
|
162
|
-
if (!seriesEntry) {
|
|
170
|
+
if (!this.seriesById.has(seriesId)) {
|
|
163
171
|
return false;
|
|
164
172
|
}
|
|
165
|
-
return this.recordSampleByKey(
|
|
173
|
+
return this.recordSampleByKey(seriesId, value, timestamp);
|
|
166
174
|
}
|
|
167
175
|
recordSampleByKey(seriesKey, value, timestamp) {
|
|
168
176
|
const series = this.seriesById.get(seriesKey);
|
|
@@ -213,8 +221,13 @@ class HistorySeriesService {
|
|
|
213
221
|
}
|
|
214
222
|
let recorded = 0;
|
|
215
223
|
leafSamples.forEach(leaf => {
|
|
216
|
-
this.
|
|
217
|
-
|
|
224
|
+
const seriesKeys = this.enabledSeriesKeysByPath.get(leaf.path);
|
|
225
|
+
if (!seriesKeys || seriesKeys.length === 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
seriesKeys.forEach(seriesKey => {
|
|
229
|
+
const series = this.seriesById.get(seriesKey);
|
|
230
|
+
if (!series) {
|
|
218
231
|
return;
|
|
219
232
|
}
|
|
220
233
|
const seriesContext = series.context ?? 'vessels.self';
|
|
@@ -293,6 +306,68 @@ class HistorySeriesService {
|
|
|
293
306
|
});
|
|
294
307
|
return Array.from(contexts).sort();
|
|
295
308
|
}
|
|
309
|
+
rebuildEnabledPathIndex() {
|
|
310
|
+
this.enabledSeriesKeysByPath.clear();
|
|
311
|
+
this.seriesById.forEach((series, seriesKey) => {
|
|
312
|
+
if (series.enabled === false) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const keys = this.enabledSeriesKeysByPath.get(series.path) ?? [];
|
|
316
|
+
keys.push(seriesKey);
|
|
317
|
+
this.enabledSeriesKeysByPath.set(series.path, keys);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
areSeriesEquivalent(left, right) {
|
|
321
|
+
const leftComparable = this.toComparableSeries(left);
|
|
322
|
+
const rightComparable = this.toComparableSeries(right);
|
|
323
|
+
return leftComparable.seriesId === rightComparable.seriesId
|
|
324
|
+
&& leftComparable.datasetUuid === rightComparable.datasetUuid
|
|
325
|
+
&& leftComparable.ownerWidgetUuid === rightComparable.ownerWidgetUuid
|
|
326
|
+
&& leftComparable.ownerWidgetSelector === rightComparable.ownerWidgetSelector
|
|
327
|
+
&& leftComparable.path === rightComparable.path
|
|
328
|
+
&& leftComparable.expansionMode === rightComparable.expansionMode
|
|
329
|
+
&& this.areStringArraysEquivalent(leftComparable.allowedBatteryIds, rightComparable.allowedBatteryIds)
|
|
330
|
+
&& leftComparable.source === rightComparable.source
|
|
331
|
+
&& leftComparable.context === rightComparable.context
|
|
332
|
+
&& leftComparable.timeScale === rightComparable.timeScale
|
|
333
|
+
&& leftComparable.period === rightComparable.period
|
|
334
|
+
&& leftComparable.retentionDurationMs === rightComparable.retentionDurationMs
|
|
335
|
+
&& leftComparable.sampleTime === rightComparable.sampleTime
|
|
336
|
+
&& leftComparable.enabled === rightComparable.enabled
|
|
337
|
+
&& this.areStringArraysEquivalent(leftComparable.methods, rightComparable.methods);
|
|
338
|
+
}
|
|
339
|
+
toComparableSeries(series) {
|
|
340
|
+
const { reconcileTs, ...comparable } = series;
|
|
341
|
+
void reconcileTs;
|
|
342
|
+
return {
|
|
343
|
+
...comparable,
|
|
344
|
+
allowedBatteryIds: this.normalizeComparableStringArray(comparable.allowedBatteryIds),
|
|
345
|
+
methods: this.normalizeComparableStringArray(comparable.methods)
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
areStringArraysEquivalent(left, right) {
|
|
349
|
+
const normalizedLeft = this.normalizeComparableStringArray(left) ?? [];
|
|
350
|
+
const normalizedRight = this.normalizeComparableStringArray(right) ?? [];
|
|
351
|
+
if (normalizedLeft.length !== normalizedRight.length) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
return normalizedLeft.every((value, index) => value === normalizedRight[index]);
|
|
355
|
+
}
|
|
356
|
+
normalizeComparableStringArray(values) {
|
|
357
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
return [...values]
|
|
361
|
+
.filter((value) => typeof value === 'string')
|
|
362
|
+
.sort((left, right) => left.localeCompare(right));
|
|
363
|
+
}
|
|
364
|
+
isChartWidget(ownerWidgetSelector, ownerWidgetUuid) {
|
|
365
|
+
if (ownerWidgetSelector === 'widget-data-chart' || ownerWidgetSelector === 'widget-windtrends-chart') {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
return ownerWidgetUuid?.startsWith('widget-windtrends-chart') === true
|
|
369
|
+
|| ownerWidgetUuid?.startsWith('widget-data-chart') === true;
|
|
370
|
+
}
|
|
296
371
|
normalizeSeries(input) {
|
|
297
372
|
const seriesId = (input.seriesId || input.datasetUuid || '').trim();
|
|
298
373
|
if (!seriesId) {
|
|
@@ -310,8 +385,16 @@ class HistorySeriesService {
|
|
|
310
385
|
if (!path) {
|
|
311
386
|
throw new Error('path is required');
|
|
312
387
|
}
|
|
313
|
-
|
|
314
|
-
const
|
|
388
|
+
const ownerWidgetSelector = typeof input.ownerWidgetSelector === 'string' ? input.ownerWidgetSelector.trim() : null;
|
|
389
|
+
const expansionMode = input.expansionMode ?? null;
|
|
390
|
+
if (expansionMode === 'bms-battery-tree' && ownerWidgetSelector !== 'widget-bms') {
|
|
391
|
+
throw new Error('BMS template series must use ownerWidgetSelector "widget-bms"');
|
|
392
|
+
}
|
|
393
|
+
const normalizedMethods = this.normalizeComparableStringArray(input.methods);
|
|
394
|
+
const normalizedAllowedBatteryIds = expansionMode === 'bms-battery-tree'
|
|
395
|
+
? this.normalizeComparableStringArray(input.allowedBatteryIds)
|
|
396
|
+
: undefined;
|
|
397
|
+
const isDataWidget = this.isChartWidget(ownerWidgetSelector, ownerWidgetUuid);
|
|
315
398
|
const retentionMs = this.resolveRetentionMs(input);
|
|
316
399
|
let sampleTime;
|
|
317
400
|
if (isDataWidget) {
|
|
@@ -324,18 +407,37 @@ class HistorySeriesService {
|
|
|
324
407
|
// a good median amount of samples for the dynamically queryable chart display range (15 min up to 24h).
|
|
325
408
|
sampleTime = 15000; // ms
|
|
326
409
|
}
|
|
327
|
-
|
|
328
|
-
...input,
|
|
410
|
+
const normalizedBase = {
|
|
329
411
|
seriesId,
|
|
330
412
|
datasetUuid,
|
|
331
413
|
ownerWidgetUuid,
|
|
414
|
+
ownerWidgetSelector,
|
|
332
415
|
path,
|
|
333
416
|
source: input.source ?? 'default',
|
|
334
417
|
context: input.context ?? 'vessels.self',
|
|
418
|
+
timeScale: input.timeScale ?? null,
|
|
419
|
+
period: Number.isFinite(input.period) ? input.period ?? null : null,
|
|
335
420
|
enabled: input.enabled !== false,
|
|
336
421
|
retentionDurationMs: retentionMs,
|
|
337
|
-
sampleTime
|
|
422
|
+
sampleTime,
|
|
423
|
+
methods: normalizedMethods,
|
|
424
|
+
reconcileTs: input.reconcileTs
|
|
338
425
|
};
|
|
426
|
+
if (expansionMode === 'bms-battery-tree') {
|
|
427
|
+
const templateSeries = {
|
|
428
|
+
...normalizedBase,
|
|
429
|
+
ownerWidgetSelector: 'widget-bms',
|
|
430
|
+
expansionMode,
|
|
431
|
+
allowedBatteryIds: normalizedAllowedBatteryIds ?? null
|
|
432
|
+
};
|
|
433
|
+
return templateSeries;
|
|
434
|
+
}
|
|
435
|
+
const concreteSeries = {
|
|
436
|
+
...normalizedBase,
|
|
437
|
+
expansionMode: null,
|
|
438
|
+
allowedBatteryIds: null
|
|
439
|
+
};
|
|
440
|
+
return concreteSeries;
|
|
339
441
|
}
|
|
340
442
|
resolveRetentionMs(series) {
|
|
341
443
|
if (Number.isFinite(series.retentionDurationMs) && series.retentionDurationMs > 0) {
|
|
@@ -408,11 +510,17 @@ class HistorySeriesService {
|
|
|
408
510
|
if (seriesContext === sampleContext) {
|
|
409
511
|
return true;
|
|
410
512
|
}
|
|
411
|
-
if (seriesContext
|
|
412
|
-
return
|
|
513
|
+
if (this.isSelfContext(seriesContext) && this.isSelfContext(sampleContext)) {
|
|
514
|
+
return true;
|
|
413
515
|
}
|
|
414
516
|
return false;
|
|
415
517
|
}
|
|
518
|
+
isSelfContext(context) {
|
|
519
|
+
if (context === 'vessels.self') {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
return !!this.selfContext && context === this.selfContext;
|
|
523
|
+
}
|
|
416
524
|
isSourceMatch(seriesSource, sampleSource) {
|
|
417
525
|
if (seriesSource === '*' || seriesSource === 'any') {
|
|
418
526
|
return true;
|