@sailingrotevista/rotevista-dash 7.0.7 → 7.0.9
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/debug.html +262 -71
- package/index.js +13 -20
- package/package.json +1 -1
package/debug.html
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>SignalK Data Debugger &
|
|
6
|
+
<title>SignalK Data Debugger & CSV Dumper (Pro v6.0)</title>
|
|
7
7
|
<style>
|
|
8
8
|
body {
|
|
9
9
|
background-color: #121212;
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
|
|
41
41
|
/* GRIGLIA WORKSPACE: AFFIANCA SELEZIONE E TABELLA VALORI */
|
|
42
42
|
.workspace-grid {
|
|
43
|
-
display:
|
|
44
|
-
|
|
45
|
-
gap: 15px;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: row;
|
|
46
45
|
flex-shrink: 0;
|
|
47
46
|
margin-bottom: 15px;
|
|
48
47
|
min-height: 0;
|
|
48
|
+
height: 380px; /* Altezza fissa dei pannelli superiori */
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/* COLONNA SINISTRA: Pannello dei filtri ad albero */
|
|
@@ -55,9 +55,13 @@
|
|
|
55
55
|
border-radius: 8px;
|
|
56
56
|
padding: 15px;
|
|
57
57
|
box-sizing: border-box;
|
|
58
|
-
|
|
58
|
+
width: 45%; /* Larghezza iniziale di default */
|
|
59
|
+
min-width: 200px;
|
|
60
|
+
max-width: 80%;
|
|
59
61
|
display: flex;
|
|
60
62
|
flex-direction: column;
|
|
63
|
+
overflow: hidden; /* Forza lo scorrimento dei figli all'interno dell'altezza fissa */
|
|
64
|
+
flex: none; /* Impedisce a Flexbox di sovrascrivere la larghezza impostata via JS durante il drag */
|
|
61
65
|
}
|
|
62
66
|
.presets {
|
|
63
67
|
margin: 10px 0;
|
|
@@ -67,6 +71,53 @@
|
|
|
67
71
|
flex-shrink: 0;
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
/* INPUT BARRA DI RICERCA VELOCE */
|
|
75
|
+
.search-input {
|
|
76
|
+
width: 100%;
|
|
77
|
+
padding: 8px 12px;
|
|
78
|
+
background: #111;
|
|
79
|
+
color: #fff;
|
|
80
|
+
border: 1px solid #333;
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
box-sizing: border-box;
|
|
83
|
+
font-family: monospace;
|
|
84
|
+
font-size: 11px;
|
|
85
|
+
margin-top: 10px;
|
|
86
|
+
outline: none;
|
|
87
|
+
transition: border-color 0.2s;
|
|
88
|
+
flex-shrink: 0;
|
|
89
|
+
}
|
|
90
|
+
.search-input:focus {
|
|
91
|
+
border-color: #4CAF50;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* DIVISIONE ORIZZONTALE RIDIMENSIONABILE A PIACIMENTO (SPLITTER DRAG BAR) */
|
|
95
|
+
.splitter {
|
|
96
|
+
width: 6px;
|
|
97
|
+
background: #222;
|
|
98
|
+
cursor: col-resize;
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
transition: background 0.15s ease;
|
|
101
|
+
border-radius: 3px;
|
|
102
|
+
margin: 0 5px;
|
|
103
|
+
touch-action: none; /* Impedisce lo scorrimento della pagina web su tablet durante il drag */
|
|
104
|
+
}
|
|
105
|
+
.splitter:hover, .splitter.dragging {
|
|
106
|
+
background: #4CAF50;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* COLONNA DESTRA: Pannello Tabella Valori Attuali */
|
|
110
|
+
.summary-panel {
|
|
111
|
+
background: #1a1a1a;
|
|
112
|
+
border: 1px solid #333;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
padding: 15px;
|
|
115
|
+
box-sizing: border-box;
|
|
116
|
+
flex-grow: 1; /* Occupa tutto lo spazio a destra dello splitter */
|
|
117
|
+
min-width: 200px;
|
|
118
|
+
overflow-y: auto;
|
|
119
|
+
}
|
|
120
|
+
|
|
70
121
|
/* STRUTTURA AD ALBERO NIDIFICATO INTERNO */
|
|
71
122
|
.trees-container {
|
|
72
123
|
margin-top: 15px;
|
|
@@ -74,7 +125,7 @@
|
|
|
74
125
|
padding-top: 15px;
|
|
75
126
|
flex-grow: 1; /* Occupa tutto lo spazio interno rimasto nel contenitore fisso */
|
|
76
127
|
overflow-y: auto;
|
|
77
|
-
display: block;
|
|
128
|
+
display: block; /* Cambiato a block per garantire il ricalcolo dello scroll interno */
|
|
78
129
|
}
|
|
79
130
|
.tree-node {
|
|
80
131
|
display: flex;
|
|
@@ -126,16 +177,6 @@
|
|
|
126
177
|
padding-left: 5px;
|
|
127
178
|
}
|
|
128
179
|
|
|
129
|
-
/* COLONNA DESTRA: Pannello Tabella Valori Attuali */
|
|
130
|
-
.summary-panel {
|
|
131
|
-
background: #1a1a1a;
|
|
132
|
-
border: 1px solid #333;
|
|
133
|
-
border-radius: 8px;
|
|
134
|
-
padding: 15px;
|
|
135
|
-
box-sizing: border-box;
|
|
136
|
-
height: 380px; /* Altezza fissa allineata alla selezione di sinistra */
|
|
137
|
-
overflow-y: auto;
|
|
138
|
-
}
|
|
139
180
|
table {
|
|
140
181
|
width: 100%;
|
|
141
182
|
border-collapse: collapse;
|
|
@@ -211,43 +252,50 @@
|
|
|
211
252
|
</head>
|
|
212
253
|
<body>
|
|
213
254
|
|
|
214
|
-
<h1>SignalK Data Debugger &
|
|
255
|
+
<h1>SignalK Data Debugger & CSV Dumper (Pro v6.0)</h1>
|
|
215
256
|
|
|
216
257
|
<div class="controls">
|
|
217
258
|
<label>Server IP:</label>
|
|
218
|
-
<input type="text" id="server-ip" value="
|
|
219
|
-
<button id="btn-scan" onclick="startScan()">
|
|
220
|
-
<button id="btn-listen" onclick="toggleListening()" class="secondary" disabled>
|
|
221
|
-
<span id="status">
|
|
259
|
+
<input type="text" id="server-ip" value="venus.local:3000">
|
|
260
|
+
<button id="btn-scan" onclick="startScan()">Scan Network (10s)</button>
|
|
261
|
+
<button id="btn-listen" onclick="toggleListening()" class="secondary" disabled>Listen</button>
|
|
262
|
+
<span id="status">DISCONNECTED</span>
|
|
222
263
|
</div>
|
|
223
264
|
|
|
224
|
-
<!-- CONTENITORE PRINCIPALE DI LAVORO
|
|
225
|
-
<div class="workspace-grid">
|
|
265
|
+
<!-- CONTENITORE PRINCIPALE DI LAVORO CON STRUTTURA FLUIDA ED ELEMENTI INTERATTIVI -->
|
|
266
|
+
<div class="workspace-grid" id="grid-container">
|
|
226
267
|
|
|
227
268
|
<!-- COLONNA SINISTRA: AREA FILTRI AVANZATA CON ALBERO GERARCHICO -->
|
|
228
|
-
<div class="filter-container">
|
|
229
|
-
<span style="font-weight: bold; color: #4CAF50; font-size: 13px;">
|
|
269
|
+
<div class="filter-container" id="pane-left">
|
|
270
|
+
<span style="font-weight: bold; color: #4CAF50; font-size: 13px;">Select paths to write to CSV Dump (Complete Hierarchical Tree):</span>
|
|
230
271
|
<div class="presets">
|
|
231
|
-
<button class="secondary" onclick="applyPreset('all')">
|
|
232
|
-
<button class="secondary" style="background-color: #2c3e50;" onclick="applyPreset('twd')">
|
|
233
|
-
<button class="secondary" style="background-color: #0088cc;" onclick="applyPreset('gps')">
|
|
234
|
-
<button class="secondary" onclick="applyPreset('none')">
|
|
272
|
+
<button class="secondary" onclick="applyPreset('all')">Select All</button>
|
|
273
|
+
<button class="secondary" style="background-color: #2c3e50;" onclick="applyPreset('twd')">Weather Compass Analysis (TWD/Wind/Course)</button>
|
|
274
|
+
<button class="secondary" style="background-color: #0088cc;" onclick="applyPreset('gps')">GPS / Course Only</button>
|
|
275
|
+
<button class="secondary" onclick="applyPreset('none')">Deselect All</button>
|
|
235
276
|
</div>
|
|
277
|
+
|
|
278
|
+
<!-- INPUT BARRA DI RICERCA VELOCE -->
|
|
279
|
+
<input type="text" id="tree-search" class="search-input" placeholder="Search path... (e.g. solar, temp, gps, batteries)" oninput="filterTree(this.value)">
|
|
280
|
+
|
|
236
281
|
<div id="path-trees-container" class="trees-container">
|
|
237
282
|
<!-- Categorie ed alberi gerarchici autogenerati dinamicamente via JS al termine della scansione -->
|
|
238
283
|
</div>
|
|
239
284
|
</div>
|
|
240
285
|
|
|
286
|
+
<!-- DIVISIONE ORIZZONTALE RIDIMENSIONABILE A PIACIMENTO (SPLITTER DRAG BAR) -->
|
|
287
|
+
<div class="splitter" id="drag-splitter"></div>
|
|
288
|
+
|
|
241
289
|
<!-- COLONNA DESTRA: PANNELLO TABELLA RIASSUNTIVA VALORI IN TEMPO REALE -->
|
|
242
|
-
<div class="summary-panel">
|
|
290
|
+
<div class="summary-panel" id="pane-right">
|
|
243
291
|
<table>
|
|
244
292
|
<thead>
|
|
245
293
|
<tr>
|
|
246
294
|
<!-- BLOCCO RIGIDO DELLE LARGHEZZE DELLE COLONNE -->
|
|
247
|
-
<th width="30%">Path
|
|
248
|
-
<th width="33%">
|
|
249
|
-
<th width="22%">
|
|
250
|
-
<th width="15%">
|
|
295
|
+
<th width="30%">Path</th>
|
|
296
|
+
<th width="33%">Converted Value</th>
|
|
297
|
+
<th width="22%">Source (Detailed Sensor)</th>
|
|
298
|
+
<th width="15%">Last Received</th>
|
|
251
299
|
</tr>
|
|
252
300
|
</thead>
|
|
253
301
|
<tbody id="data-table">
|
|
@@ -261,11 +309,11 @@
|
|
|
261
309
|
<!-- PANNELLO LOG SEQUENZIALE (CSV) - POSIZIONATO A TUTTA LARGHEZZA SUL FONDO -->
|
|
262
310
|
<div class="log-panel">
|
|
263
311
|
<div class="log-header">
|
|
264
|
-
<span>Event
|
|
312
|
+
<span>CSV Event Dump (Timestamp, Path, Value, Unit, Source, Spike)</span>
|
|
265
313
|
<div style="display: flex; align-items: center;">
|
|
266
|
-
<span id="copy-notification">
|
|
267
|
-
<button class="action" onclick="copyLogCSV()" style="padding: 4px 10px; font-size: 11px; margin-right: 10px;">
|
|
268
|
-
<button class="secondary" onclick="clearLog()" style="padding: 4px 10px; font-size: 11px;">
|
|
314
|
+
<span id="copy-notification">Copied!</span>
|
|
315
|
+
<button class="action" onclick="copyLogCSV()" style="padding: 4px 10px; font-size: 11px; margin-right: 10px;">Copy Dump</button>
|
|
316
|
+
<button class="secondary" onclick="clearLog()" style="padding: 4px 10px; font-size: 11px;">Clear</button>
|
|
269
317
|
</div>
|
|
270
318
|
</div>
|
|
271
319
|
|
|
@@ -348,7 +396,7 @@
|
|
|
348
396
|
if (hasChildren) {
|
|
349
397
|
const toggleSpan = document.createElement('span');
|
|
350
398
|
toggleSpan.className = 'node-toggle';
|
|
351
|
-
toggleSpan.innerText = '[
|
|
399
|
+
toggleSpan.innerText = '[+]'; // Compresso di default all'avvio
|
|
352
400
|
toggleSpan.onclick = (e) => {
|
|
353
401
|
e.stopPropagation();
|
|
354
402
|
const childContainer = nodeDiv.querySelector(':scope > .node-children');
|
|
@@ -370,7 +418,7 @@
|
|
|
370
418
|
// 2. Checkbox di selezione
|
|
371
419
|
const chk = document.createElement('input');
|
|
372
420
|
chk.type = 'checkbox';
|
|
373
|
-
chk.checked =
|
|
421
|
+
chk.checked = false; // Deselezionato di default per focalizzare lo spazio
|
|
374
422
|
chk.className = 'node-checkbox';
|
|
375
423
|
|
|
376
424
|
if (!hasChildren) {
|
|
@@ -393,10 +441,11 @@
|
|
|
393
441
|
|
|
394
442
|
nodeDiv.appendChild(rowDiv);
|
|
395
443
|
|
|
396
|
-
// 4. Creazione ricorsiva dei nodi figli (Sotto-Albero)
|
|
444
|
+
// 4. Creazione ricorsiva dei nodi figli (Sotto-Albero nascosto di default all'avvio)
|
|
397
445
|
if (hasChildren) {
|
|
398
446
|
const childrenDiv = document.createElement('div');
|
|
399
447
|
childrenDiv.className = 'node-children';
|
|
448
|
+
childrenDiv.style.display = 'none'; // Chiuso di default all'avvio
|
|
400
449
|
renderTreeDOM(item._children, childrenDiv, false);
|
|
401
450
|
nodeDiv.appendChild(childrenDiv);
|
|
402
451
|
}
|
|
@@ -424,21 +473,103 @@
|
|
|
424
473
|
|
|
425
474
|
// 3. FILTRAGGIO DINAMICO DELLA TABELLA: Aggiorna istantaneamente la visibilità dei valori
|
|
426
475
|
updateTableVisibility();
|
|
476
|
+
|
|
477
|
+
// 4. FILTRAGGIO DINAMICO DEL DUMP CSV: Aggiorna lo schermo dei log al cambio di selezione
|
|
478
|
+
renderLogTextArea();
|
|
427
479
|
});
|
|
428
480
|
|
|
429
|
-
//
|
|
481
|
+
// Filtra l'albero gerarchico visivo in tempo reale con espansione automatica dei rami corrispondenti
|
|
482
|
+
function filterTree(query) {
|
|
483
|
+
const q = query.toLowerCase().trim();
|
|
484
|
+
const leaves = document.querySelectorAll('.tree-leaf');
|
|
485
|
+
const branches = Array.from(document.querySelectorAll('.tree-branch')).reverse(); // Scansione invertita per propagare lo stato ai nonni
|
|
486
|
+
|
|
487
|
+
if (q === "") {
|
|
488
|
+
// Ripristina la visibilità totale e richiude i rami se la query viene cancellata
|
|
489
|
+
leaves.forEach(leaf => {
|
|
490
|
+
leaf.style.display = '';
|
|
491
|
+
});
|
|
492
|
+
branches.forEach(branch => {
|
|
493
|
+
branch.style.display = '';
|
|
494
|
+
const childContainer = branch.querySelector(':scope > .node-children');
|
|
495
|
+
const toggleSpan = branch.querySelector(':scope > .node-row > .node-toggle');
|
|
496
|
+
if (childContainer && toggleSpan) {
|
|
497
|
+
childContainer.style.display = 'none';
|
|
498
|
+
toggleSpan.innerText = '[+]';
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
updateTableVisibility();
|
|
502
|
+
renderLogTextArea();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 1. Filtra tutte le foglie (Parametri finali) e AUTO-SELEZIONA quelle corrispondenti
|
|
507
|
+
leaves.forEach(leaf => {
|
|
508
|
+
const labelEl = leaf.querySelector('.node-label');
|
|
509
|
+
const chk = leaf.querySelector('input');
|
|
510
|
+
if (!labelEl || !chk) return;
|
|
511
|
+
|
|
512
|
+
const label = labelEl.innerText.toLowerCase();
|
|
513
|
+
const path = (chk.dataset.path || "").toLowerCase();
|
|
514
|
+
|
|
515
|
+
if (label.includes(q) || path.includes(q)) {
|
|
516
|
+
leaf.style.display = ''; // Corrispondenza trovata
|
|
517
|
+
chk.checked = true; // AUTO-SELEZIONE dei parametri corrispondenti!
|
|
518
|
+
} else {
|
|
519
|
+
leaf.style.display = 'none'; // Nasconde se non corrisponde
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// 2. Filtra e auto-espande i rami genitori dal basso verso l'alto
|
|
524
|
+
branches.forEach(branch => {
|
|
525
|
+
const childContainer = branch.querySelector(':scope > .node-children');
|
|
526
|
+
const toggleSpan = branch.querySelector(':scope > .node-row > .node-toggle');
|
|
527
|
+
if (!childContainer) return;
|
|
528
|
+
|
|
529
|
+
// Conta quanti sotto-nodi (sia rami che foglie) sono rimasti visibili
|
|
530
|
+
const visibleChildren = childContainer.querySelectorAll('.tree-node:not([style*="display: none"])');
|
|
531
|
+
|
|
532
|
+
if (visibleChildren.length > 0) {
|
|
533
|
+
branch.style.display = ''; // Mostra il ramo genitore poiché contiene elementi corrispondenti
|
|
534
|
+
if (toggleSpan) {
|
|
535
|
+
childContainer.style.display = 'flex'; // Auto-espande la cartella per mostrare il match
|
|
536
|
+
toggleSpan.innerText = '[-]';
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
branch.style.display = 'none'; // Nasconde l'intera cartella se nessun figlio corrisponde alla ricerca
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// 3. Ricalcola gli stati indeterminati dei genitori e aggiorna la tabella in tempo reale
|
|
544
|
+
updateAllTreeMasterCheckboxes();
|
|
545
|
+
updateTableVisibility();
|
|
546
|
+
renderLogTextArea(); // Filtra il Dump CSV sullo schermo ad ogni input della ricerca!
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Aggiorna istantaneamente la visibilità dei valori della tabella in base alle spunte e alla barra di ricerca
|
|
430
550
|
function updateTableVisibility() {
|
|
551
|
+
const queryInput = document.getElementById('tree-search');
|
|
552
|
+
const q = queryInput ? queryInput.value.toLowerCase().trim() : "";
|
|
553
|
+
|
|
431
554
|
registeredPaths.forEach(path => {
|
|
432
555
|
const safeId = path.replace(/\./g, '-');
|
|
433
556
|
const chk = document.getElementById(`chk-${safeId}`);
|
|
434
557
|
const row = document.getElementById(`row-${safeId}`);
|
|
435
558
|
|
|
436
559
|
if (row) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
row.style.display = '';
|
|
560
|
+
if (isScanning) {
|
|
561
|
+
// Durante la scansione mostriamo tutte le righe scoperte in tempo reale
|
|
562
|
+
row.style.display = '';
|
|
440
563
|
} else {
|
|
441
|
-
|
|
564
|
+
// Fuori scansione la riga è visibile solo se è spuntata E se il percorso corrisponde alla ricerca attiva (se presente)
|
|
565
|
+
const matchesQuery = q === "" || path.toLowerCase().includes(q);
|
|
566
|
+
const isChecked = chk && chk.checked;
|
|
567
|
+
|
|
568
|
+
if (isChecked && matchesQuery) {
|
|
569
|
+
row.style.display = ''; // Mostra
|
|
570
|
+
} else {
|
|
571
|
+
row.style.display = 'none'; // Nasconde
|
|
572
|
+
}
|
|
442
573
|
}
|
|
443
574
|
}
|
|
444
575
|
});
|
|
@@ -483,6 +614,7 @@
|
|
|
483
614
|
});
|
|
484
615
|
updateAllTreeMasterCheckboxes();
|
|
485
616
|
updateTableVisibility(); // Sincronizza la tabella visiva al cambio di preset
|
|
617
|
+
renderLogTextArea(); // Filtra dinamicamente anche i log CSV in base al preset!
|
|
486
618
|
}
|
|
487
619
|
|
|
488
620
|
// =========================================================
|
|
@@ -515,6 +647,7 @@
|
|
|
515
647
|
pathTree = {};
|
|
516
648
|
document.getElementById('path-trees-container').innerHTML = '';
|
|
517
649
|
document.getElementById('data-table').innerHTML = '';
|
|
650
|
+
document.getElementById('tree-search').value = ''; // Resetta il testo della barra di ricerca
|
|
518
651
|
|
|
519
652
|
const btnScan = document.getElementById('btn-scan');
|
|
520
653
|
const btnListen = document.getElementById('btn-listen');
|
|
@@ -526,12 +659,12 @@
|
|
|
526
659
|
scanCountdown = 10;
|
|
527
660
|
|
|
528
661
|
btnScan.disabled = true;
|
|
529
|
-
btnScan.innerText = `
|
|
662
|
+
btnScan.innerText = `Scanning... ${scanCountdown}s`;
|
|
530
663
|
btnListen.disabled = true;
|
|
531
664
|
btnListen.className = "secondary";
|
|
532
|
-
btnListen.innerText = "
|
|
665
|
+
btnListen.innerText = "Listen";
|
|
533
666
|
|
|
534
|
-
status.innerText = "
|
|
667
|
+
status.innerText = "SCAN IN PROGRESS";
|
|
535
668
|
status.className = "scanning";
|
|
536
669
|
|
|
537
670
|
socket = new WebSocket(`ws://${ip}/signalk/v1/stream?subscribe=self`);
|
|
@@ -543,26 +676,27 @@
|
|
|
543
676
|
scanTimer = setInterval(() => {
|
|
544
677
|
scanCountdown--;
|
|
545
678
|
if (scanCountdown > 0) {
|
|
546
|
-
btnScan.innerText = `
|
|
679
|
+
btnScan.innerText = `Scanning... ${scanCountdown}s`;
|
|
547
680
|
} else {
|
|
548
681
|
// Scansione terminata! Generazione ricorsiva dell'albero gerarchico reale
|
|
549
682
|
clearInterval(scanTimer);
|
|
550
683
|
scanTimer = null;
|
|
551
|
-
|
|
684
|
+
|
|
685
|
+
isScanning = false; // 1. Imposta lo stato a false PRIMA di chiudere la connessione
|
|
686
|
+
socket.close(); // 2. Scatena l'evento onclose (che ora ignorerà l'interruzione di scansione)
|
|
552
687
|
|
|
553
688
|
const treeContainer = document.getElementById('path-trees-container');
|
|
554
689
|
renderTreeDOM(pathTree, treeContainer, true); // Genera ricorsivamente l'albero
|
|
555
690
|
updateAllTreeMasterCheckboxes(); // Allinea gli stati dei padri
|
|
556
|
-
updateTableVisibility(); // Inizializza i filtri della tabella
|
|
691
|
+
updateTableVisibility(); // Inizializza i filtri della tabella (Nasconde tutto all'avvio)
|
|
557
692
|
|
|
558
|
-
isScanning = false;
|
|
559
693
|
btnScan.disabled = false;
|
|
560
|
-
btnScan.innerText = "
|
|
694
|
+
btnScan.innerText = "Scan Network (10s)";
|
|
561
695
|
|
|
562
696
|
btnListen.disabled = false;
|
|
563
697
|
btnListen.className = "";
|
|
564
698
|
|
|
565
|
-
status.innerText = "
|
|
699
|
+
status.innerText = "SCAN COMPLETED";
|
|
566
700
|
status.className = "online";
|
|
567
701
|
addLogEntry(new Date().toISOString(), "SYSTEM", "Scan Phase Finished", "-", "-", false);
|
|
568
702
|
}
|
|
@@ -596,8 +730,8 @@
|
|
|
596
730
|
clearInterval(scanTimer);
|
|
597
731
|
isScanning = false;
|
|
598
732
|
btnScan.disabled = false;
|
|
599
|
-
btnScan.innerText = "
|
|
600
|
-
status.innerText = "
|
|
733
|
+
btnScan.innerText = "Scan Network (10s)";
|
|
734
|
+
status.innerText = "SCAN FAILED";
|
|
601
735
|
status.className = "";
|
|
602
736
|
}
|
|
603
737
|
};
|
|
@@ -615,10 +749,10 @@
|
|
|
615
749
|
if (isListening) {
|
|
616
750
|
if (socket) socket.close();
|
|
617
751
|
isListening = false;
|
|
618
|
-
btnListen.innerText = "
|
|
752
|
+
btnListen.innerText = "Listen";
|
|
619
753
|
btnListen.className = "";
|
|
620
754
|
btnScan.disabled = false;
|
|
621
|
-
status.innerText = "
|
|
755
|
+
status.innerText = "LISTENING STOPPED";
|
|
622
756
|
status.className = "online";
|
|
623
757
|
addLogEntry(new Date().toISOString(), "SYSTEM", "Listening Stopped", "-", "-", false);
|
|
624
758
|
return;
|
|
@@ -626,10 +760,10 @@
|
|
|
626
760
|
|
|
627
761
|
isListening = true;
|
|
628
762
|
btnScan.disabled = true;
|
|
629
|
-
btnListen.innerText = "Stop
|
|
763
|
+
btnListen.innerText = "Stop Dump";
|
|
630
764
|
btnListen.className = "stop";
|
|
631
765
|
|
|
632
|
-
status.innerText = "
|
|
766
|
+
status.innerText = "DUMPING VALUES...";
|
|
633
767
|
status.className = "online";
|
|
634
768
|
|
|
635
769
|
lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
@@ -654,11 +788,20 @@
|
|
|
654
788
|
const safeId = v.path.replace(/\./g, '-');
|
|
655
789
|
const chk = document.getElementById(`chk-${safeId}`);
|
|
656
790
|
|
|
791
|
+
// Verifica sia la spunta sia l'effettiva visibilità a schermo (non nascosto da ricerca!)
|
|
792
|
+
let isLogged = false;
|
|
657
793
|
if (chk && chk.checked) {
|
|
658
|
-
|
|
794
|
+
const leaf = chk.closest('.tree-leaf');
|
|
795
|
+
if (leaf && leaf.style.display !== 'none') {
|
|
796
|
+
isLogged = true;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (isLogged) {
|
|
801
|
+
// Scrive e logga nel CSV solo se la checkbox corrispondente è spuntata e visibile!
|
|
659
802
|
processData(v.path, v.value, sourceName, timeISO, timeUI, true);
|
|
660
803
|
} else {
|
|
661
|
-
// Se non è spuntata, aggiorna passivamente la tabella silenziando i log
|
|
804
|
+
// Se non è spuntata o è nascosta, aggiorna passivamente la tabella silenziando i log
|
|
662
805
|
if (registeredPaths.has(v.path)) {
|
|
663
806
|
processData(v.path, v.value, sourceName, timeISO, timeUI, false);
|
|
664
807
|
}
|
|
@@ -675,7 +818,7 @@
|
|
|
675
818
|
socket.onclose = () => {
|
|
676
819
|
if (isListening) {
|
|
677
820
|
isListening = false;
|
|
678
|
-
btnListen.innerText = "
|
|
821
|
+
btnListen.innerText = "Listen";
|
|
679
822
|
btnListen.className = "";
|
|
680
823
|
btnScan.disabled = false;
|
|
681
824
|
status.innerText = "DISCONNESSO";
|
|
@@ -685,7 +828,7 @@
|
|
|
685
828
|
};
|
|
686
829
|
|
|
687
830
|
socket.onerror = () => {
|
|
688
|
-
status.innerText = "
|
|
831
|
+
status.innerText = "CONNECTION ERROR";
|
|
689
832
|
addLogEntry(new Date().toISOString(), "SYSTEM", "Connection Error", "-", "-", true);
|
|
690
833
|
};
|
|
691
834
|
}
|
|
@@ -803,19 +946,36 @@
|
|
|
803
946
|
renderLogTextArea();
|
|
804
947
|
}
|
|
805
948
|
|
|
949
|
+
// Filtra ricorsivamente i log visualizzati nel Dump CSV in tempo reale in base alla selezione attiva e visibile
|
|
806
950
|
function renderLogTextArea() {
|
|
807
951
|
const textArea = document.getElementById('log-container');
|
|
808
|
-
|
|
952
|
+
if (!textArea) return;
|
|
953
|
+
|
|
954
|
+
const filteredLines = logLinesArray.filter(line => {
|
|
955
|
+
const parts = line.split(',');
|
|
956
|
+
const path = parts[1]; // L'indice 1 corrisponde al percorso dati del pacchetto
|
|
957
|
+
if (!path || path === "Path" || path === "SYSTEM") return true; // Preserva le intestazioni e i messaggi di connessione di sistema
|
|
958
|
+
|
|
959
|
+
const safeId = path.replace(/\./g, '-');
|
|
960
|
+
const chk = document.getElementById(`chk-${safeId}`);
|
|
961
|
+
|
|
962
|
+
// Filtro integrato: la linea viene visualizzata solo se spuntata E visibile (non esclusa dalla ricerca)
|
|
963
|
+
if (chk && chk.checked) {
|
|
964
|
+
const leaf = chk.closest('.tree-leaf');
|
|
965
|
+
return leaf && leaf.style.display !== 'none';
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
textArea.value = csvHeader + "\n" + filteredLines.join("\n");
|
|
809
971
|
textArea.scrollTop = textArea.scrollHeight;
|
|
810
972
|
}
|
|
811
973
|
|
|
812
|
-
// Pulisce l'area di testo dei log sequenziali
|
|
813
974
|
function clearLog() {
|
|
814
975
|
logLinesArray = [];
|
|
815
976
|
renderLogTextArea();
|
|
816
977
|
}
|
|
817
978
|
|
|
818
|
-
// Copia negli appunti il testo del log CSV
|
|
819
979
|
function copyLogCSV() {
|
|
820
980
|
const textArea = document.getElementById('log-container');
|
|
821
981
|
textArea.select();
|
|
@@ -830,17 +990,48 @@
|
|
|
830
990
|
}
|
|
831
991
|
} catch (err) {
|
|
832
992
|
console.error("Copia fallita: ", err);
|
|
833
|
-
alert("
|
|
993
|
+
alert("Error copying to clipboard. Please use CTRL+C / CMD+C manually.");
|
|
834
994
|
}
|
|
835
995
|
}
|
|
836
996
|
|
|
837
|
-
// Mostra e sfuma la notifica di copia riuscita
|
|
838
997
|
function showCopyNotif() {
|
|
839
998
|
const notif = document.getElementById('copy-notification');
|
|
840
999
|
notif.style.opacity = 1;
|
|
841
1000
|
setTimeout(() => notif.style.opacity = 0, 2000);
|
|
842
1001
|
window.getSelection().removeAllRanges();
|
|
843
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
// =========================================================
|
|
1005
|
+
// LOGICA DI GESTIONE DELLO SPLITTER ORIZZONTALE (DRAG BAR)
|
|
1006
|
+
// =========================================================
|
|
1007
|
+
const splitter = document.getElementById('drag-splitter');
|
|
1008
|
+
const leftPane = document.getElementById('pane-left');
|
|
1009
|
+
const gridContainer = document.getElementById('grid-container');
|
|
1010
|
+
|
|
1011
|
+
splitter.addEventListener('pointerdown', (e) => {
|
|
1012
|
+
e.preventDefault();
|
|
1013
|
+
splitter.classList.add('dragging');
|
|
1014
|
+
|
|
1015
|
+
const startX = e.clientX;
|
|
1016
|
+
const startWidth = leftPane.getBoundingClientRect().width;
|
|
1017
|
+
const containerWidth = gridContainer.getBoundingClientRect().width;
|
|
1018
|
+
|
|
1019
|
+
const onPointerMove = (moveEvent) => {
|
|
1020
|
+
const deltaX = moveEvent.clientX - startX;
|
|
1021
|
+
// Limita il ridimensionamento tra 200px e l'80% dello schermo per sicurezza
|
|
1022
|
+
const newWidth = Math.max(200, Math.min(containerWidth * 0.8, startWidth + deltaX));
|
|
1023
|
+
leftPane.style.width = `${(newWidth / containerWidth) * 100}%`;
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
const onPointerUp = () => {
|
|
1027
|
+
splitter.classList.remove('dragging');
|
|
1028
|
+
document.removeEventListener('pointermove', onPointerMove);
|
|
1029
|
+
document.removeEventListener('pointerup', onPointerUp);
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
document.addEventListener('pointermove', onPointerMove);
|
|
1033
|
+
document.addEventListener('pointerup', onPointerUp);
|
|
1034
|
+
});
|
|
844
1035
|
</script>
|
|
845
1036
|
</body>
|
|
846
1037
|
</html>
|
package/index.js
CHANGED
|
@@ -45,26 +45,19 @@ module.exports = function (app) {
|
|
|
45
45
|
let futureForecast = null; // Memorizza la previsione futura { timestamp, tws, twd }
|
|
46
46
|
let lastForecast30mSlot = 0; // Orario dell'ultimo blocco orologio scaricato con successo (es. 15:30)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
raw = {};
|
|
62
|
-
windRadarSlots = []; // Reset degli archi storici della bussola
|
|
63
|
-
lastFrozen30mSlot = 0; // Reset del monitor temporale della bussola
|
|
64
|
-
futureForecast = null; // Reset della previsione meteo futura
|
|
65
|
-
lastForecast30mSlot = 0; // Reset del monitor temporale di scaricamento meteo
|
|
66
|
-
|
|
67
|
-
// 2. Registra le rotte API solo la prima volta (Abilitate per CORS remoto)
|
|
48
|
+
/**
|
|
49
|
+
* plugin.start: Inizializza il plugin.
|
|
50
|
+
* Viene chiamato all'avvio e OGNI VOLTA che si salva nella configurazione.
|
|
51
|
+
*/
|
|
52
|
+
plugin.start = function (options) {
|
|
53
|
+
// 1. Aggiorna la configurazione in memoria
|
|
54
|
+
currentConfig = options;
|
|
55
|
+
app.debug(`${plugin.name} started/updated with new options`);
|
|
56
|
+
|
|
57
|
+
// Rimosso il reset distruttivo per garantire la conservazione dei dati in RAM
|
|
58
|
+
// durante il salvataggio dei parametri o il cambio delle calibrazioni delle scale.
|
|
59
|
+
|
|
60
|
+
// 2. Registra le rotte API solo la prima volta (Abilitate per CORS remoto)
|
|
68
61
|
if (!routeRegistered) {
|
|
69
62
|
app.get('/rotevista-config', (req, res) => {
|
|
70
63
|
res.header("Access-Control-Allow-Origin", "*"); // Sblocca la Dashboard locale su Mac
|