@sailingrotevista/rotevista-dash 7.0.6 → 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.
Files changed (2) hide show
  1. package/debug.html +437 -133
  2. package/package.json +1 -1
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
- /* Pannello dei filtri avanzati */
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
- margin-bottom: 20px;
39
- flex-shrink: 0;
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
- .checkbox-grid {
48
- display: grid;
49
- grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
50
- gap: 8px;
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: 10px;
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
- .checkbox-item {
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
- font-size: 11px;
60
- color: #ccc;
61
- cursor: pointer;
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
- .checkbox-item input {
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
- /* Layout a due pannelli (Sopra Tabella, Sotto Log) */
68
- .panels-container {
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
- gap: 20px;
72
- flex-grow: 1;
73
- min-height: 0; /* Essenziale per far funzionare lo scroll nel flexbox */
124
+ border-left: 1px dashed #2c2c2c;
125
+ margin-left: 11px;
126
+ padding-left: 5px;
74
127
  }
75
128
 
76
- /* Pannello Superiore: Tabella Valori Attuali */
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: 10px;
83
- overflow-x: auto;
84
- max-height: 250px;
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 (Evita ridimensionamenti continui) */
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 dentro le colonne fisse */
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
- /* Pannello Inferiore: Log Sequenziale in Formato Testo (simil-CSV) */
162
+ /* PANNELLO INFERIORE: Log Sequenziale in Formato Testo (Spazio flessibile rimanente) */
111
163
  .log-panel {
112
- flex: 1 1 auto;
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: 200px;
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-connect" onclick="toggleConnection()">Connetti</button>
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
- <!-- AREA FILTRI AVANZATA CON CHECKBOX E PRESET -->
172
- <div class="filter-container">
173
- <span style="font-weight: bold; color: #4CAF50; font-size: 13px;">Seleziona percorsi da scrivere nel Log CSV:</span>
174
- <div class="presets">
175
- <button class="secondary" onclick="applyPreset('all')">Seleziona Tutto</button>
176
- <button class="secondary" style="background-color: #2c3e50;" onclick="applyPreset('twd')">Analisi Bussola Meteo (TWD/Vento/Rotta)</button>
177
- <button class="secondary" style="background-color: #0088cc;" onclick="applyPreset('gps')">Solo GPS / Rotte</button>
178
- <button class="secondary" onclick="applyPreset('none')">Deseleziona Tutto</button>
179
- </div>
180
- <div id="path-checkboxes" class="checkbox-grid">
181
- <!-- Generate via JS -->
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
- <div class="panels-container">
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="25%">Path (Percorso)</th>
194
- <th width="31.5%">Valore Convertito</th>
195
- <th width="31.5%">Sorgente (Sensore - Dettagliato)</th>
196
- <th width="12%">Ultimo Ricevuto</th>
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
- <!-- PANNELLO LOG SEQUENZIALE (CSV) -->
206
- <div class="log-panel">
207
- <div class="log-header">
208
- <span>Event Log CSV (Timestamp, Path, Value, Unit, Source, Spike)</span>
209
- <div style="display: flex; align-items: center;">
210
- <span id="copy-notification">Copiato!</span>
211
- <button class="action" onclick="copyLogCSV()" style="padding: 4px 10px; font-size: 11px; margin-right: 10px;">Copia CSV</button>
212
- <button class="secondary" onclick="clearLog()" style="padding: 4px 10px; font-size: 11px;">Pulisci</button>
213
- </div>
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
- const pathsToWatch = [
223
- "navigation.position",
224
- "navigation.position.latitude",
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
- // Inizializza la Tabella Riassuntiva e genera le Checkbox
248
- const tbody = document.getElementById('data-table');
249
- const checkboxGrid = document.getElementById('path-checkboxes');
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 righe Tabella
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. Creazione Checkbox
266
- const label = document.createElement('label');
267
- label.className = 'checkbox-item';
268
- label.innerHTML = `
269
- <input type="checkbox" id="chk-${safeId}" checked>
270
- <span>${path}</span>
271
- `;
272
- checkboxGrid.appendChild(label);
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
- const csvHeader = "Timestamp,Path,Value,Unit,Source,IsSpike";
276
- document.getElementById('log-container').value = csvHeader + "\n";
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
- pathsToWatch.forEach(path => {
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
- // Analisi strategica bussola vento ( Heading, COG, AWA, TWA, TWD, TWS, AWS )
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
- // GESTIONE CONNESSIONE WEBSOCKET
506
+ // FASE 1: AVVIO SCANSIONE DI SCOPERTA (DISCOVERY - 10s)
322
507
  // =========================================================
323
- function toggleConnection() {
324
- const btn = document.getElementById('btn-connect');
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
- if (socket && socket.readyState === WebSocket.OPEN) {
329
- socket.close();
330
- btn.innerText = "Connetti";
331
- btn.className = "";
332
- status.innerText = "DISCONNESSO";
333
- status.className = "";
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
- btn.innerText = "Disconnetti";
338
- btn.className = "stop";
339
- status.innerText = "CONNESSIONE IN CORSO...";
340
- status.className = "";
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
- status.innerText = "CONNESSO (In ascolto...)";
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
- if (pathsToWatch.includes(v.path)) {
365
- processData(v.path, v.value, sourceName, timeISO, timeUI);
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 elaborazione messaggio: ", err);
671
+ console.error("Errore ascolto:", err);
373
672
  }
374
673
  };
375
674
 
376
675
  socket.onclose = () => {
377
- btn.innerText = "Connetti";
378
- btn.className = "";
379
- status.innerText = "DISCONNESSO";
380
- status.className = "";
381
- addLogEntry(new Date().toISOString(), "SYSTEM", "Disconnected", "-", "-", false);
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 = (error) => {
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)};${rawValue.longitude.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 (Solo ed esclusivamente se la checkbox di questo percorso è spuntata!)
443
- const safeId = path.replace(/\./g, '-');
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(`time-${safeId}`);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "7.0.6",
3
+ "version": "7.0.7",
4
4
  "description": "Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {