@sailingrotevista/rotevista-dash 7.0.5 → 7.0.7
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/app.js +1 -1
- package/charts.js +24 -19
- package/debug.html +437 -133
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -23,7 +23,7 @@ let CONFIG = {
|
|
|
23
23
|
minSpeed: 0.5,
|
|
24
24
|
stabilityBreakout: 15
|
|
25
25
|
},
|
|
26
|
-
graphs: { reef1:
|
|
26
|
+
graphs: { reef1: 5, reef2: 10, historyMinutes: 10, samples: 60 },
|
|
27
27
|
scales: {
|
|
28
28
|
stw: { stdMax: 4, hercSpan: 2, step: 1 },
|
|
29
29
|
sog: { stdMax: 4, hercSpan: 2, step: 1 },
|
package/charts.js
CHANGED
|
@@ -179,25 +179,30 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
179
179
|
const colDepth = "#0088cc", colStw = "#00C851", colSog = "#ffbb33", colVmg = "#00b8d4";
|
|
180
180
|
|
|
181
181
|
const getColorProps = (val) => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
182
|
+
const baseStroke = isFocused ? "4.2" : "1.6";
|
|
183
|
+
const alertStroke = isFocused ? "4.8" : "2.2";
|
|
184
|
+
const warnStroke = isFocused ? "4.4" : "1.8";
|
|
185
|
+
|
|
186
|
+
let color = colTws, opacity = "0.15", stroke = baseStroke;
|
|
187
|
+
if (isTws) {
|
|
188
|
+
const baseWind = (displayModeTws === 'AWS') ? colAws : colTws;
|
|
189
|
+
const r1 = CONFIG.graphs.reef1 || 15;
|
|
190
|
+
const r2 = CONFIG.graphs.reef2 || 20;
|
|
191
|
+
const r3 = r2 + (r2 - r1); // Calcolo sintetico del 3° Terzarolo (Storm)
|
|
192
|
+
|
|
193
|
+
if (val >= r3) { color = "#9c27b0"; opacity = "0.65"; stroke = alertStroke; } // Viola (Tempesta / 3a Mano)
|
|
194
|
+
else if (val >= r2) { color = colDanger; opacity = "0.55"; stroke = alertStroke; } // Rosso (Pericolo / 2a Mano)
|
|
195
|
+
else if (val >= r1) { color = colWarning; opacity = "0.45"; stroke = warnStroke; } // Arancione (Allerta / 1a Mano)
|
|
196
|
+
else color = baseWind;
|
|
197
|
+
} else if (isDepth) {
|
|
198
|
+
if (val < CONFIG.alarms.depthDanger) { color = colDanger; opacity = "0.55"; stroke = alertStroke; }
|
|
199
|
+
else if (val < CONFIG.alarms.depthWarning) { color = colWarning; opacity = "0.45"; stroke = warnStroke; }
|
|
200
|
+
else color = colDepth;
|
|
201
|
+
} else {
|
|
202
|
+
if (id === 'stw-graph') color = colStw;
|
|
203
|
+
else if (id === 'sog-graph') color = (displayModeSog === 'VMG') ? colVmg : colSog;
|
|
204
|
+
}
|
|
205
|
+
return { color, opacity, stroke };
|
|
201
206
|
};
|
|
202
207
|
|
|
203
208
|
let grids = "";
|
package/debug.html
CHANGED
|
@@ -26,76 +26,128 @@
|
|
|
26
26
|
button.stop { background: #f44336; }
|
|
27
27
|
button.secondary { background: #555; }
|
|
28
28
|
button.action { background: #2196F3; } /* Tasto Copia Blu */
|
|
29
|
+
|
|
30
|
+
button:disabled {
|
|
31
|
+
background: #2a2a2a !important;
|
|
32
|
+
color: #666 !important;
|
|
33
|
+
cursor: not-allowed !important;
|
|
34
|
+
border-color: #333 !important;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
#status { font-weight: bold; color: #f44336; margin-left: auto; }
|
|
30
38
|
#status.online { color: #4CAF50; }
|
|
39
|
+
#status.scanning { color: #ff9800; }
|
|
31
40
|
|
|
32
|
-
/*
|
|
41
|
+
/* GRIGLIA WORKSPACE: AFFIANCA SELEZIONE E TABELLA VALORI */
|
|
42
|
+
.workspace-grid {
|
|
43
|
+
display: grid;
|
|
44
|
+
grid-template-columns: 1fr 1.3fr;
|
|
45
|
+
gap: 15px;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
margin-bottom: 15px;
|
|
48
|
+
min-height: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* COLONNA SINISTRA: Pannello dei filtri ad albero */
|
|
33
52
|
.filter-container {
|
|
34
53
|
background: #1a1a1a;
|
|
35
54
|
border: 1px solid #333;
|
|
36
55
|
border-radius: 8px;
|
|
37
56
|
padding: 15px;
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
height: 380px; /* Altezza fissa allineata alla tabella di destra */
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
40
61
|
}
|
|
41
62
|
.presets {
|
|
42
63
|
margin: 10px 0;
|
|
43
64
|
display: flex;
|
|
44
65
|
gap: 10px;
|
|
45
66
|
flex-wrap: wrap;
|
|
67
|
+
flex-shrink: 0;
|
|
46
68
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
margin-top: 12px;
|
|
69
|
+
|
|
70
|
+
/* STRUTTURA AD ALBERO NIDIFICATO INTERNO */
|
|
71
|
+
.trees-container {
|
|
72
|
+
margin-top: 15px;
|
|
52
73
|
border-top: 1px solid #222;
|
|
53
|
-
padding-top:
|
|
74
|
+
padding-top: 15px;
|
|
75
|
+
flex-grow: 1; /* Occupa tutto lo spazio interno rimasto nel contenitore fisso */
|
|
76
|
+
overflow-y: auto;
|
|
77
|
+
display: block;
|
|
54
78
|
}
|
|
55
|
-
.
|
|
79
|
+
.tree-node {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
margin-left: 18px;
|
|
83
|
+
}
|
|
84
|
+
.tree-node.root-level {
|
|
85
|
+
margin-left: 0;
|
|
86
|
+
margin-bottom: 6px;
|
|
87
|
+
background: #0d0d0d;
|
|
88
|
+
border: 1px solid #222;
|
|
89
|
+
border-radius: 6px;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
}
|
|
92
|
+
.node-row {
|
|
56
93
|
display: flex;
|
|
57
94
|
align-items: center;
|
|
58
95
|
gap: 8px;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
96
|
+
padding: 6px 10px;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
user-select: none;
|
|
99
|
+
}
|
|
100
|
+
.root-level > .node-row {
|
|
101
|
+
background: #181818;
|
|
102
|
+
font-weight: bold;
|
|
103
|
+
color: #4CAF50;
|
|
104
|
+
border-bottom: 1px solid #222;
|
|
62
105
|
}
|
|
63
|
-
.
|
|
106
|
+
.node-toggle {
|
|
64
107
|
cursor: pointer;
|
|
108
|
+
color: #888;
|
|
109
|
+
font-weight: bold;
|
|
110
|
+
width: 24px;
|
|
111
|
+
text-align: center;
|
|
112
|
+
font-size: 11px;
|
|
65
113
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
114
|
+
.node-toggle:hover {
|
|
115
|
+
color: #4CAF50;
|
|
116
|
+
}
|
|
117
|
+
.node-label {
|
|
118
|
+
color: #ccc;
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
}
|
|
121
|
+
.node-children {
|
|
69
122
|
display: flex;
|
|
70
123
|
flex-direction: column;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
124
|
+
border-left: 1px dashed #2c2c2c;
|
|
125
|
+
margin-left: 11px;
|
|
126
|
+
padding-left: 5px;
|
|
74
127
|
}
|
|
75
128
|
|
|
76
|
-
/*
|
|
129
|
+
/* COLONNA DESTRA: Pannello Tabella Valori Attuali */
|
|
77
130
|
.summary-panel {
|
|
78
|
-
flex: 0 0 auto;
|
|
79
131
|
background: #1a1a1a;
|
|
80
132
|
border: 1px solid #333;
|
|
81
133
|
border-radius: 8px;
|
|
82
|
-
padding:
|
|
83
|
-
|
|
84
|
-
|
|
134
|
+
padding: 15px;
|
|
135
|
+
box-sizing: border-box;
|
|
136
|
+
height: 380px; /* Altezza fissa allineata alla selezione di sinistra */
|
|
85
137
|
overflow-y: auto;
|
|
86
138
|
}
|
|
87
139
|
table {
|
|
88
140
|
width: 100%;
|
|
89
141
|
border-collapse: collapse;
|
|
90
142
|
font-size: 13px;
|
|
91
|
-
table-layout: fixed; /* BLOCCO RIGIDO DELLE COLONNE
|
|
143
|
+
table-layout: fixed; /* BLOCCO RIGIDO DELLE COLONNE */
|
|
92
144
|
}
|
|
93
145
|
th, td {
|
|
94
146
|
border: 1px solid #333;
|
|
95
147
|
padding: 6px 8px;
|
|
96
148
|
text-align: left;
|
|
97
149
|
overflow: hidden;
|
|
98
|
-
word-wrap: break-word; /* Forza l'andamento a capo dei testi lunghi
|
|
150
|
+
word-wrap: break-word; /* Forza l'andamento a capo dei testi lunghi */
|
|
99
151
|
}
|
|
100
152
|
th { background-color: #222; color: #aaa; text-transform: uppercase; position: sticky; top: 0; z-index: 10; }
|
|
101
153
|
.val-cell { min-width: 120px; }
|
|
@@ -107,15 +159,15 @@
|
|
|
107
159
|
.time { color: #90caf9; font-size: 11px; }
|
|
108
160
|
.raw { color: #777; font-size: 10px; display: block; margin-top: 2px; }
|
|
109
161
|
|
|
110
|
-
/*
|
|
162
|
+
/* PANNELLO INFERIORE: Log Sequenziale in Formato Testo (Spazio flessibile rimanente) */
|
|
111
163
|
.log-panel {
|
|
112
|
-
flex: 1
|
|
164
|
+
flex-grow: 1; /* Occupa tutto lo spazio verticale rimanente della finestra */
|
|
113
165
|
background: #0a0a0a;
|
|
114
166
|
border: 1px solid #333;
|
|
115
167
|
border-radius: 8px;
|
|
116
168
|
display: flex;
|
|
117
169
|
flex-direction: column;
|
|
118
|
-
min-height:
|
|
170
|
+
min-height: 150px;
|
|
119
171
|
}
|
|
120
172
|
.log-header {
|
|
121
173
|
background: #222;
|
|
@@ -164,36 +216,38 @@
|
|
|
164
216
|
<div class="controls">
|
|
165
217
|
<label>Server IP:</label>
|
|
166
218
|
<input type="text" id="server-ip" value="192.168.111.240:3000">
|
|
167
|
-
<button id="btn-
|
|
219
|
+
<button id="btn-scan" onclick="startScan()">Scansiona Rete (10s)</button>
|
|
220
|
+
<button id="btn-listen" onclick="toggleListening()" class="secondary" disabled>Ascolta</button>
|
|
168
221
|
<span id="status">DISCONNESSO</span>
|
|
169
222
|
</div>
|
|
170
223
|
|
|
171
|
-
<!--
|
|
172
|
-
<div class="
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
224
|
+
<!-- CONTENITORE PRINCIPALE DI LAVORO A DUE COLONNE -->
|
|
225
|
+
<div class="workspace-grid">
|
|
226
|
+
|
|
227
|
+
<!-- COLONNA SINISTRA: AREA FILTRI AVANZATA CON ALBERO GERARCHICO -->
|
|
228
|
+
<div class="filter-container">
|
|
229
|
+
<span style="font-weight: bold; color: #4CAF50; font-size: 13px;">Seleziona percorsi da scrivere nel Log CSV (Albero Gerarchico Completo):</span>
|
|
230
|
+
<div class="presets">
|
|
231
|
+
<button class="secondary" onclick="applyPreset('all')">Seleziona Tutto</button>
|
|
232
|
+
<button class="secondary" style="background-color: #2c3e50;" onclick="applyPreset('twd')">Analisi Bussola Meteo (TWD/Vento/Rotta)</button>
|
|
233
|
+
<button class="secondary" style="background-color: #0088cc;" onclick="applyPreset('gps')">Solo GPS / Rotte</button>
|
|
234
|
+
<button class="secondary" onclick="applyPreset('none')">Deseleziona Tutto</button>
|
|
235
|
+
</div>
|
|
236
|
+
<div id="path-trees-container" class="trees-container">
|
|
237
|
+
<!-- Categorie ed alberi gerarchici autogenerati dinamicamente via JS al termine della scansione -->
|
|
238
|
+
</div>
|
|
182
239
|
</div>
|
|
183
|
-
</div>
|
|
184
240
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<!-- PANNELLO TABELLA RIASSUNTIVA -->
|
|
241
|
+
<!-- COLONNA DESTRA: PANNELLO TABELLA RIASSUNTIVA VALORI IN TEMPO REALE -->
|
|
188
242
|
<div class="summary-panel">
|
|
189
243
|
<table>
|
|
190
244
|
<thead>
|
|
191
245
|
<tr>
|
|
192
246
|
<!-- BLOCCO RIGIDO DELLE LARGHEZZE DELLE COLONNE -->
|
|
193
|
-
<th width="
|
|
194
|
-
<th width="
|
|
195
|
-
<th width="
|
|
196
|
-
<th width="
|
|
247
|
+
<th width="30%">Path (Percorso)</th>
|
|
248
|
+
<th width="33%">Valore Convertito</th>
|
|
249
|
+
<th width="22%">Sorgente (Sensore - Dettagliato)</th>
|
|
250
|
+
<th width="15%">Ultimo Ricevuto</th>
|
|
197
251
|
</tr>
|
|
198
252
|
</thead>
|
|
199
253
|
<tbody id="data-table">
|
|
@@ -202,56 +256,54 @@
|
|
|
202
256
|
</table>
|
|
203
257
|
</div>
|
|
204
258
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
</
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<!-- PANNELLO LOG SEQUENZIALE (CSV) - POSIZIONATO A TUTTA LARGHEZZA SUL FONDO -->
|
|
262
|
+
<div class="log-panel">
|
|
263
|
+
<div class="log-header">
|
|
264
|
+
<span>Event Log CSV (Timestamp, Path, Value, Unit, Source, Spike)</span>
|
|
265
|
+
<div style="display: flex; align-items: center;">
|
|
266
|
+
<span id="copy-notification">Copiato!</span>
|
|
267
|
+
<button class="action" onclick="copyLogCSV()" style="padding: 4px 10px; font-size: 11px; margin-right: 10px;">Copia CSV</button>
|
|
268
|
+
<button class="secondary" onclick="clearLog()" style="padding: 4px 10px; font-size: 11px;">Pulisci</button>
|
|
214
269
|
</div>
|
|
215
|
-
|
|
216
|
-
<textarea id="log-container" readonly></textarea>
|
|
217
270
|
</div>
|
|
218
|
-
|
|
271
|
+
|
|
272
|
+
<textarea id="log-container" readonly></textarea>
|
|
219
273
|
</div>
|
|
220
274
|
|
|
221
275
|
<script>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"navigation.position.longitude",
|
|
226
|
-
"navigation.speedThroughWater",
|
|
227
|
-
"navigation.speedOverGround",
|
|
228
|
-
"navigation.headingTrue",
|
|
229
|
-
"navigation.courseOverGroundTrue",
|
|
230
|
-
"environment.wind.speedApparent",
|
|
231
|
-
"environment.wind.angleApparent",
|
|
232
|
-
"environment.wind.speedTrue",
|
|
233
|
-
"environment.wind.angleTrueWater",
|
|
234
|
-
"environment.wind.directionTrue",
|
|
235
|
-
"environment.depth.belowTransducer"
|
|
236
|
-
];
|
|
276
|
+
// Registro dello stato dati e del database ad albero
|
|
277
|
+
const registeredPaths = new Set();
|
|
278
|
+
let pathTree = {}; // Albero gerarchico nidificato in memoria RAM
|
|
237
279
|
|
|
238
280
|
let socket = null;
|
|
239
281
|
let logLinesArray = [];
|
|
240
282
|
const MAX_LOG_LINES = 1000;
|
|
241
283
|
|
|
284
|
+
// Stato del sistema
|
|
285
|
+
let isScanning = false;
|
|
286
|
+
let isListening = false;
|
|
287
|
+
let scanTimer = null;
|
|
288
|
+
let scanCountdown = 10;
|
|
289
|
+
|
|
242
290
|
let lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
243
291
|
|
|
244
292
|
const radToDeg = (rad) => rad * (180 / Math.PI);
|
|
245
293
|
const msToKts = (ms) => ms * 1.94384;
|
|
246
294
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
295
|
+
const csvHeader = "Timestamp,Path,Value,Unit,Source,IsSpike";
|
|
296
|
+
document.getElementById('log-container').value = csvHeader + "\n";
|
|
297
|
+
|
|
298
|
+
// Inserisce e mappa dinamicamente un nuovo percorso nella tabella e nella struttura ad albero logica
|
|
299
|
+
function registerNewPath(path) {
|
|
300
|
+
if (registeredPaths.has(path)) return;
|
|
301
|
+
registeredPaths.add(path);
|
|
250
302
|
|
|
251
|
-
pathsToWatch.forEach(path => {
|
|
252
303
|
const safeId = path.replace(/\./g, '-');
|
|
253
304
|
|
|
254
|
-
// 1. Creazione
|
|
305
|
+
// 1. Creazione dinamica della riga nella tabella riassuntiva
|
|
306
|
+
const tbody = document.getElementById('data-table');
|
|
255
307
|
const tr = document.createElement('tr');
|
|
256
308
|
tr.id = `row-${safeId}`;
|
|
257
309
|
tr.innerHTML = `
|
|
@@ -262,24 +314,157 @@
|
|
|
262
314
|
`;
|
|
263
315
|
tbody.appendChild(tr);
|
|
264
316
|
|
|
265
|
-
// 2.
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
317
|
+
// 2. Costruzione della struttura gerarchica in memoria (RAM) per la generazione successiva
|
|
318
|
+
const parts = path.split('.');
|
|
319
|
+
let current = pathTree;
|
|
320
|
+
parts.forEach((part, index) => {
|
|
321
|
+
if (!current[part]) {
|
|
322
|
+
current[part] = {
|
|
323
|
+
_name: part,
|
|
324
|
+
_fullPath: parts.slice(0, index + 1).join('.'),
|
|
325
|
+
_isLeaf: (index === parts.length - 1),
|
|
326
|
+
_children: {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
current = current[part]._children;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Disegna ricorsivamente l'albero gerarchico nel DOM (Lazy Rendering)
|
|
334
|
+
function renderTreeDOM(node, container, isRoot = false) {
|
|
335
|
+
const keys = Object.keys(node).sort();
|
|
336
|
+
|
|
337
|
+
keys.forEach(key => {
|
|
338
|
+
const item = node[key];
|
|
339
|
+
const hasChildren = Object.keys(item._children).length > 0;
|
|
340
|
+
|
|
341
|
+
const nodeDiv = document.createElement('div');
|
|
342
|
+
nodeDiv.className = `tree-node ${isRoot ? 'root-level' : ''} ${hasChildren ? 'tree-branch' : 'tree-leaf'}`;
|
|
343
|
+
|
|
344
|
+
const rowDiv = document.createElement('div');
|
|
345
|
+
rowDiv.className = 'node-row';
|
|
346
|
+
|
|
347
|
+
// 1. Pulsante Espandi/Collassa per i rami dotati di figli
|
|
348
|
+
if (hasChildren) {
|
|
349
|
+
const toggleSpan = document.createElement('span');
|
|
350
|
+
toggleSpan.className = 'node-toggle';
|
|
351
|
+
toggleSpan.innerText = '[-]';
|
|
352
|
+
toggleSpan.onclick = (e) => {
|
|
353
|
+
e.stopPropagation();
|
|
354
|
+
const childContainer = nodeDiv.querySelector(':scope > .node-children');
|
|
355
|
+
if (childContainer.style.display === 'none') {
|
|
356
|
+
childContainer.style.display = 'flex';
|
|
357
|
+
toggleSpan.innerText = '[-]';
|
|
358
|
+
} else {
|
|
359
|
+
childContainer.style.display = 'none';
|
|
360
|
+
toggleSpan.innerText = '[+]';
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
rowDiv.appendChild(toggleSpan);
|
|
364
|
+
} else {
|
|
365
|
+
const spacer = document.createElement('span');
|
|
366
|
+
spacer.className = 'node-toggle';
|
|
367
|
+
rowDiv.appendChild(spacer);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 2. Checkbox di selezione
|
|
371
|
+
const chk = document.createElement('input');
|
|
372
|
+
chk.type = 'checkbox';
|
|
373
|
+
chk.checked = true;
|
|
374
|
+
chk.className = 'node-checkbox';
|
|
375
|
+
|
|
376
|
+
if (!hasChildren) {
|
|
377
|
+
const safeId = item._fullPath.replace(/\./g, '-');
|
|
378
|
+
chk.id = `chk-${safeId}`;
|
|
379
|
+
chk.dataset.path = item._fullPath;
|
|
380
|
+
} else {
|
|
381
|
+
const category = item._fullPath;
|
|
382
|
+
chk.id = `master-chk-${category.replace(/\./g, '-')}`;
|
|
383
|
+
chk.dataset.branchCategory = category;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
rowDiv.appendChild(chk);
|
|
387
|
+
|
|
388
|
+
// 3. Etichetta del Nodo
|
|
389
|
+
const labelSpan = document.createElement('span');
|
|
390
|
+
labelSpan.className = 'node-label';
|
|
391
|
+
labelSpan.innerText = key;
|
|
392
|
+
rowDiv.appendChild(labelSpan);
|
|
393
|
+
|
|
394
|
+
nodeDiv.appendChild(rowDiv);
|
|
395
|
+
|
|
396
|
+
// 4. Creazione ricorsiva dei nodi figli (Sotto-Albero)
|
|
397
|
+
if (hasChildren) {
|
|
398
|
+
const childrenDiv = document.createElement('div');
|
|
399
|
+
childrenDiv.className = 'node-children';
|
|
400
|
+
renderTreeDOM(item._children, childrenDiv, false);
|
|
401
|
+
nodeDiv.appendChild(childrenDiv);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
container.appendChild(nodeDiv);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// DELEGAZIONE DEGLI EVENTI SUL CONTENITORE: Gestisce lo scorrimento e la propagazione degli stati
|
|
409
|
+
document.getElementById('path-trees-container').addEventListener('change', (e) => {
|
|
410
|
+
const chk = e.target;
|
|
411
|
+
if (!chk || chk.type !== 'checkbox') return;
|
|
412
|
+
|
|
413
|
+
// 1. PROPAGAZIONE VERSO IL BASSO: Se clicchi un nodo padre, spunta/disattiva tutti i suoi discendenti
|
|
414
|
+
const nodeDiv = chk.closest('.tree-node');
|
|
415
|
+
if (nodeDiv) {
|
|
416
|
+
const childCheckboxes = nodeDiv.querySelectorAll('.node-checkbox');
|
|
417
|
+
childCheckboxes.forEach(c => {
|
|
418
|
+
c.checked = chk.checked;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 2. CASCATA VERSO L'ALTO: Ricalcola gli stati dei padri (incluso lo stato indeterminato "-")
|
|
423
|
+
updateAllTreeMasterCheckboxes();
|
|
424
|
+
|
|
425
|
+
// 3. FILTRAGGIO DINAMICO DELLA TABELLA: Aggiorna istantaneamente la visibilità dei valori
|
|
426
|
+
updateTableVisibility();
|
|
273
427
|
});
|
|
274
428
|
|
|
275
|
-
|
|
276
|
-
|
|
429
|
+
// Aggiorna istantaneamente la visibilità dei valori della tabella in base alle spunte
|
|
430
|
+
function updateTableVisibility() {
|
|
431
|
+
registeredPaths.forEach(path => {
|
|
432
|
+
const safeId = path.replace(/\./g, '-');
|
|
433
|
+
const chk = document.getElementById(`chk-${safeId}`);
|
|
434
|
+
const row = document.getElementById(`row-${safeId}`);
|
|
435
|
+
|
|
436
|
+
if (row) {
|
|
437
|
+
// Durante la scansione mostriamo tutto. Fuori scansione mostriamo solo se la spunta corrispondente è attiva.
|
|
438
|
+
if (isScanning || !chk || chk.checked) {
|
|
439
|
+
row.style.display = ''; // Mostra (table-row)
|
|
440
|
+
} else {
|
|
441
|
+
row.style.display = 'none'; // Nasconde
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Ricalcola ricorsivamente gli stati (Checked, Unchecked, Indeterminate) dal basso verso l'alto
|
|
448
|
+
function updateAllTreeMasterCheckboxes() {
|
|
449
|
+
const branchNodes = Array.from(document.querySelectorAll('.tree-branch')).reverse();
|
|
450
|
+
|
|
451
|
+
branchNodes.forEach(branchNode => {
|
|
452
|
+
const masterCheckbox = branchNode.querySelector(':scope > .node-row > .node-checkbox');
|
|
453
|
+
if (!masterCheckbox) return;
|
|
454
|
+
|
|
455
|
+
const childCheckboxes = branchNode.querySelectorAll('.tree-leaf input[type="checkbox"]');
|
|
456
|
+
const checkedCount = Array.from(childCheckboxes).filter(c => c.checked).length;
|
|
457
|
+
|
|
458
|
+
masterCheckbox.checked = (checkedCount === childCheckboxes.length);
|
|
459
|
+
masterCheckbox.indeterminate = (checkedCount > 0 && checkedCount < childCheckboxes.length);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
277
462
|
|
|
278
463
|
// =========================================================
|
|
279
|
-
// PRESET RAPIDI DI SELEZIONE
|
|
464
|
+
// PRESET RAPIDI DI SELEZIONE (Mappati sulle foglie attive)
|
|
280
465
|
// =========================================================
|
|
281
466
|
function applyPreset(preset) {
|
|
282
|
-
|
|
467
|
+
registeredPaths.forEach(path => {
|
|
283
468
|
const safeId = path.replace(/\./g, '-');
|
|
284
469
|
const chk = document.getElementById(`chk-${safeId}`);
|
|
285
470
|
if (!chk) return;
|
|
@@ -289,15 +474,15 @@
|
|
|
289
474
|
} else if (preset === 'none') {
|
|
290
475
|
chk.checked = false;
|
|
291
476
|
} else if (preset === 'gps') {
|
|
292
|
-
// Solo GPS, SOG e COG
|
|
293
477
|
const isGps = path.includes('position') || path === 'navigation.speedOverGround' || path === 'navigation.courseOverGroundTrue';
|
|
294
478
|
chk.checked = isGps;
|
|
295
479
|
} else if (preset === 'twd') {
|
|
296
|
-
|
|
297
|
-
const isTwd = path.includes('wind') || path === 'navigation.headingTrue' || path === 'navigation.courseOverGroundTrue';
|
|
480
|
+
const isTwd = path.includes('wind') || path === 'navigation.headingTrue' || path === 'navigation.courseOverGroundTrue' || path === 'navigation.headingMagnetic';
|
|
298
481
|
chk.checked = isTwd;
|
|
299
482
|
}
|
|
300
483
|
});
|
|
484
|
+
updateAllTreeMasterCheckboxes();
|
|
485
|
+
updateTableVisibility(); // Sincronizza la tabella visiva al cambio di preset
|
|
301
486
|
}
|
|
302
487
|
|
|
303
488
|
// =========================================================
|
|
@@ -318,41 +503,146 @@
|
|
|
318
503
|
}
|
|
319
504
|
|
|
320
505
|
// =========================================================
|
|
321
|
-
//
|
|
506
|
+
// FASE 1: AVVIO SCANSIONE DI SCOPERTA (DISCOVERY - 10s)
|
|
322
507
|
// =========================================================
|
|
323
|
-
function
|
|
324
|
-
|
|
508
|
+
function startScan() {
|
|
509
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
510
|
+
socket.close();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Pulisce l'albero logico e l'interfaccia prima del nuovo avvio
|
|
514
|
+
registeredPaths.clear();
|
|
515
|
+
pathTree = {};
|
|
516
|
+
document.getElementById('path-trees-container').innerHTML = '';
|
|
517
|
+
document.getElementById('data-table').innerHTML = '';
|
|
518
|
+
|
|
519
|
+
const btnScan = document.getElementById('btn-scan');
|
|
520
|
+
const btnListen = document.getElementById('btn-listen');
|
|
325
521
|
const status = document.getElementById('status');
|
|
326
522
|
const ip = document.getElementById('server-ip').value;
|
|
327
523
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
524
|
+
isScanning = true;
|
|
525
|
+
isListening = false;
|
|
526
|
+
scanCountdown = 10;
|
|
527
|
+
|
|
528
|
+
btnScan.disabled = true;
|
|
529
|
+
btnScan.innerText = `Scansione... ${scanCountdown}s`;
|
|
530
|
+
btnListen.disabled = true;
|
|
531
|
+
btnListen.className = "secondary";
|
|
532
|
+
btnListen.innerText = "Ascolta";
|
|
533
|
+
|
|
534
|
+
status.innerText = "SCANSIONE IN CORSO";
|
|
535
|
+
status.className = "scanning";
|
|
536
|
+
|
|
537
|
+
socket = new WebSocket(`ws://${ip}/signalk/v1/stream?subscribe=self`);
|
|
538
|
+
|
|
539
|
+
socket.onopen = () => {
|
|
540
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Scan Phase Started", "-", ip, false);
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
scanTimer = setInterval(() => {
|
|
544
|
+
scanCountdown--;
|
|
545
|
+
if (scanCountdown > 0) {
|
|
546
|
+
btnScan.innerText = `Scansione... ${scanCountdown}s`;
|
|
547
|
+
} else {
|
|
548
|
+
// Scansione terminata! Generazione ricorsiva dell'albero gerarchico reale
|
|
549
|
+
clearInterval(scanTimer);
|
|
550
|
+
scanTimer = null;
|
|
551
|
+
socket.close();
|
|
552
|
+
|
|
553
|
+
const treeContainer = document.getElementById('path-trees-container');
|
|
554
|
+
renderTreeDOM(pathTree, treeContainer, true); // Genera ricorsivamente l'albero
|
|
555
|
+
updateAllTreeMasterCheckboxes(); // Allinea gli stati dei padri
|
|
556
|
+
updateTableVisibility(); // Inizializza i filtri della tabella
|
|
557
|
+
|
|
558
|
+
isScanning = false;
|
|
559
|
+
btnScan.disabled = false;
|
|
560
|
+
btnScan.innerText = "Scansiona Rete (10s)";
|
|
561
|
+
|
|
562
|
+
btnListen.disabled = false;
|
|
563
|
+
btnListen.className = "";
|
|
564
|
+
|
|
565
|
+
status.innerText = "SCANSIONE COMPLETATA";
|
|
566
|
+
status.className = "online";
|
|
567
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Scan Phase Finished", "-", "-", false);
|
|
568
|
+
}
|
|
569
|
+
}, 1000);
|
|
570
|
+
|
|
571
|
+
socket.onmessage = (event) => {
|
|
572
|
+
try {
|
|
573
|
+
const data = JSON.parse(event.data);
|
|
574
|
+
if (data.updates) {
|
|
575
|
+
data.updates.forEach(update => {
|
|
576
|
+
const sourceName = extractSourceIdentifier(update);
|
|
577
|
+
const timeISO = new Date(update.timestamp).toISOString();
|
|
578
|
+
const timeUI = new Date(update.timestamp).toLocaleTimeString() + '.' + new Date(update.timestamp).getMilliseconds().toString().padStart(3, '0');
|
|
579
|
+
|
|
580
|
+
if (update.values) {
|
|
581
|
+
update.values.forEach(v => {
|
|
582
|
+
// Popola e accumula i percorsi in memoria ad albero
|
|
583
|
+
registerNewPath(v.path);
|
|
584
|
+
processData(v.path, v.value, sourceName, timeISO, timeUI, false); // Nessun log scritto
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
} catch (err) {
|
|
590
|
+
console.error("Errore scansione:", err);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
socket.onclose = () => {
|
|
595
|
+
if (isScanning) {
|
|
596
|
+
clearInterval(scanTimer);
|
|
597
|
+
isScanning = false;
|
|
598
|
+
btnScan.disabled = false;
|
|
599
|
+
btnScan.innerText = "Scansiona Rete (10s)";
|
|
600
|
+
status.innerText = "SCANSIONE FALLITA";
|
|
601
|
+
status.className = "";
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// =========================================================
|
|
607
|
+
// FASE 2: ASCOLTO SELETTIVO (LOG ATTIVI SOLO SE SPUNTATI)
|
|
608
|
+
// =========================================================
|
|
609
|
+
function toggleListening() {
|
|
610
|
+
const btnListen = document.getElementById('btn-listen');
|
|
611
|
+
const btnScan = document.getElementById('btn-scan');
|
|
612
|
+
const status = document.getElementById('status');
|
|
613
|
+
const ip = document.getElementById('server-ip').value;
|
|
614
|
+
|
|
615
|
+
if (isListening) {
|
|
616
|
+
if (socket) socket.close();
|
|
617
|
+
isListening = false;
|
|
618
|
+
btnListen.innerText = "Ascolta";
|
|
619
|
+
btnListen.className = "";
|
|
620
|
+
btnScan.disabled = false;
|
|
621
|
+
status.innerText = "ASCOLTO INTERROTTO";
|
|
622
|
+
status.className = "online";
|
|
623
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Listening Stopped", "-", "-", false);
|
|
334
624
|
return;
|
|
335
625
|
}
|
|
336
626
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
627
|
+
isListening = true;
|
|
628
|
+
btnScan.disabled = true;
|
|
629
|
+
btnListen.innerText = "Stop Ascolto";
|
|
630
|
+
btnListen.className = "stop";
|
|
631
|
+
|
|
632
|
+
status.innerText = "IN ASCOLTO SELETTIVO...";
|
|
633
|
+
status.className = "online";
|
|
341
634
|
|
|
342
635
|
lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
343
636
|
|
|
344
637
|
socket = new WebSocket(`ws://${ip}/signalk/v1/stream?subscribe=self`);
|
|
345
638
|
|
|
346
639
|
socket.onopen = () => {
|
|
347
|
-
|
|
348
|
-
status.className = "online";
|
|
349
|
-
addLogEntry(new Date().toISOString(), "SYSTEM", "Connected", "-", ip, false);
|
|
640
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Selective Listening Started", "-", ip, false);
|
|
350
641
|
};
|
|
351
642
|
|
|
352
643
|
socket.onmessage = (event) => {
|
|
353
644
|
try {
|
|
354
645
|
const data = JSON.parse(event.data);
|
|
355
|
-
|
|
356
646
|
if (data.updates) {
|
|
357
647
|
data.updates.forEach(update => {
|
|
358
648
|
const sourceName = extractSourceIdentifier(update);
|
|
@@ -361,36 +651,49 @@
|
|
|
361
651
|
|
|
362
652
|
if (update.values) {
|
|
363
653
|
update.values.forEach(v => {
|
|
364
|
-
|
|
365
|
-
|
|
654
|
+
const safeId = v.path.replace(/\./g, '-');
|
|
655
|
+
const chk = document.getElementById(`chk-${safeId}`);
|
|
656
|
+
|
|
657
|
+
if (chk && chk.checked) {
|
|
658
|
+
// Scrive e logga nel CSV solo se la checkbox corrispondente è spuntata!
|
|
659
|
+
processData(v.path, v.value, sourceName, timeISO, timeUI, true);
|
|
660
|
+
} else {
|
|
661
|
+
// Se non è spuntata, aggiorna passivamente la tabella silenziando i log
|
|
662
|
+
if (registeredPaths.has(v.path)) {
|
|
663
|
+
processData(v.path, v.value, sourceName, timeISO, timeUI, false);
|
|
664
|
+
}
|
|
366
665
|
}
|
|
367
666
|
});
|
|
368
667
|
}
|
|
369
668
|
});
|
|
370
669
|
}
|
|
371
670
|
} catch (err) {
|
|
372
|
-
console.error("Errore
|
|
671
|
+
console.error("Errore ascolto:", err);
|
|
373
672
|
}
|
|
374
673
|
};
|
|
375
674
|
|
|
376
675
|
socket.onclose = () => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
676
|
+
if (isListening) {
|
|
677
|
+
isListening = false;
|
|
678
|
+
btnListen.innerText = "Ascolta";
|
|
679
|
+
btnListen.className = "";
|
|
680
|
+
btnScan.disabled = false;
|
|
681
|
+
status.innerText = "DISCONNESSO";
|
|
682
|
+
status.className = "";
|
|
683
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Connection Closed", "-", "-", false);
|
|
684
|
+
}
|
|
382
685
|
};
|
|
383
|
-
|
|
384
|
-
socket.onerror = (
|
|
385
|
-
status.innerText = "ERRORE";
|
|
386
|
-
addLogEntry(new Date().toISOString(), "SYSTEM", "Error", "-", "-", true);
|
|
686
|
+
|
|
687
|
+
socket.onerror = () => {
|
|
688
|
+
status.innerText = "ERRORE CONNESSIONE";
|
|
689
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Connection Error", "-", "-", true);
|
|
387
690
|
};
|
|
388
691
|
}
|
|
389
692
|
|
|
390
693
|
// =========================================================
|
|
391
694
|
// ELABORAZIONE DATI E SPIKE DETECTION
|
|
392
695
|
// =========================================================
|
|
393
|
-
function processData(path, rawValue, source, timeISO, timeUI) {
|
|
696
|
+
function processData(path, rawValue, source, timeISO, timeUI, writeToCsv) {
|
|
394
697
|
let formattedVal = "---";
|
|
395
698
|
let unit = "";
|
|
396
699
|
let isSpike = false;
|
|
@@ -398,7 +701,7 @@
|
|
|
398
701
|
if (rawValue !== null && rawValue !== undefined) {
|
|
399
702
|
if (path === "navigation.position") {
|
|
400
703
|
if (typeof rawValue === 'object' && rawValue.latitude !== undefined && rawValue.longitude !== undefined) {
|
|
401
|
-
formattedVal = `${rawValue.latitude.toFixed(6)}
|
|
704
|
+
formattedVal = `${rawValue.latitude.toFixed(6)}`;
|
|
402
705
|
unit = "lat;lon";
|
|
403
706
|
} else {
|
|
404
707
|
formattedVal = "Invalid Position";
|
|
@@ -439,10 +742,8 @@
|
|
|
439
742
|
// 1. Aggiorna la Tabella in alto
|
|
440
743
|
updateTableRow(path, formattedVal, unit, rawValue, source, timeUI);
|
|
441
744
|
|
|
442
|
-
// 2. Aggiungi riga al Log CSV
|
|
443
|
-
|
|
444
|
-
const chk = document.getElementById(`chk-${safeId}`);
|
|
445
|
-
if (chk && chk.checked) {
|
|
745
|
+
// 2. Aggiungi riga al Log CSV
|
|
746
|
+
if (writeToCsv) {
|
|
446
747
|
addLogEntry(timeISO, path, formattedVal, unit, source, isSpike);
|
|
447
748
|
}
|
|
448
749
|
}
|
|
@@ -471,7 +772,7 @@
|
|
|
471
772
|
srcCell.innerHTML = `<span class="source">${source}</span>`;
|
|
472
773
|
}
|
|
473
774
|
|
|
474
|
-
const timeCell = document.getElementById(
|
|
775
|
+
const timeCell = document.getElementById('time-' + safeId);
|
|
475
776
|
if (timeCell) {
|
|
476
777
|
timeCell.innerHTML = `<span class="time">${timeUI}</span>`;
|
|
477
778
|
}
|
|
@@ -508,11 +809,13 @@
|
|
|
508
809
|
textArea.scrollTop = textArea.scrollHeight;
|
|
509
810
|
}
|
|
510
811
|
|
|
812
|
+
// Pulisce l'area di testo dei log sequenziali
|
|
511
813
|
function clearLog() {
|
|
512
814
|
logLinesArray = [];
|
|
513
815
|
renderLogTextArea();
|
|
514
816
|
}
|
|
515
817
|
|
|
818
|
+
// Copia negli appunti il testo del log CSV
|
|
516
819
|
function copyLogCSV() {
|
|
517
820
|
const textArea = document.getElementById('log-container');
|
|
518
821
|
textArea.select();
|
|
@@ -531,6 +834,7 @@
|
|
|
531
834
|
}
|
|
532
835
|
}
|
|
533
836
|
|
|
837
|
+
// Mostra e sfuma la notifica di copia riuscita
|
|
534
838
|
function showCopyNotif() {
|
|
535
839
|
const notif = document.getElementById('copy-notification');
|
|
536
840
|
notif.style.opacity = 1;
|