@sailingrotevista/rotevista-dash 7.0.6 → 7.0.8
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 +642 -147
- 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;
|
|
@@ -26,76 +26,169 @@
|
|
|
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: flex;
|
|
44
|
+
flex-direction: row;
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
margin-bottom: 15px;
|
|
47
|
+
min-height: 0;
|
|
48
|
+
height: 380px; /* Altezza fissa dei pannelli superiori */
|
|
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
|
+
width: 45%; /* Larghezza iniziale di default */
|
|
59
|
+
min-width: 200px;
|
|
60
|
+
max-width: 80%;
|
|
61
|
+
display: flex;
|
|
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 */
|
|
40
65
|
}
|
|
41
66
|
.presets {
|
|
42
67
|
margin: 10px 0;
|
|
43
68
|
display: flex;
|
|
44
69
|
gap: 10px;
|
|
45
70
|
flex-wrap: wrap;
|
|
71
|
+
flex-shrink: 0;
|
|
46
72
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
|
|
121
|
+
/* STRUTTURA AD ALBERO NIDIFICATO INTERNO */
|
|
122
|
+
.trees-container {
|
|
123
|
+
margin-top: 15px;
|
|
52
124
|
border-top: 1px solid #222;
|
|
53
|
-
padding-top:
|
|
125
|
+
padding-top: 15px;
|
|
126
|
+
flex-grow: 1; /* Occupa tutto lo spazio interno rimasto nel contenitore fisso */
|
|
127
|
+
overflow-y: auto;
|
|
128
|
+
display: block; /* Cambiato a block per garantire il ricalcolo dello scroll interno */
|
|
54
129
|
}
|
|
55
|
-
.
|
|
130
|
+
.tree-node {
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
margin-left: 18px;
|
|
134
|
+
}
|
|
135
|
+
.tree-node.root-level {
|
|
136
|
+
margin-left: 0;
|
|
137
|
+
margin-bottom: 6px;
|
|
138
|
+
background: #0d0d0d;
|
|
139
|
+
border: 1px solid #222;
|
|
140
|
+
border-radius: 6px;
|
|
141
|
+
overflow: hidden;
|
|
142
|
+
}
|
|
143
|
+
.node-row {
|
|
56
144
|
display: flex;
|
|
57
145
|
align-items: center;
|
|
58
146
|
gap: 8px;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
147
|
+
padding: 6px 10px;
|
|
148
|
+
font-size: 12px;
|
|
149
|
+
user-select: none;
|
|
62
150
|
}
|
|
63
|
-
.
|
|
151
|
+
.root-level > .node-row {
|
|
152
|
+
background: #181818;
|
|
153
|
+
font-weight: bold;
|
|
154
|
+
color: #4CAF50;
|
|
155
|
+
border-bottom: 1px solid #222;
|
|
156
|
+
}
|
|
157
|
+
.node-toggle {
|
|
64
158
|
cursor: pointer;
|
|
159
|
+
color: #888;
|
|
160
|
+
font-weight: bold;
|
|
161
|
+
width: 24px;
|
|
162
|
+
text-align: center;
|
|
163
|
+
font-size: 11px;
|
|
65
164
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
165
|
+
.node-toggle:hover {
|
|
166
|
+
color: #4CAF50;
|
|
167
|
+
}
|
|
168
|
+
.node-label {
|
|
169
|
+
color: #ccc;
|
|
170
|
+
font-size: 11px;
|
|
171
|
+
}
|
|
172
|
+
.node-children {
|
|
69
173
|
display: flex;
|
|
70
174
|
flex-direction: column;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
175
|
+
border-left: 1px dashed #2c2c2c;
|
|
176
|
+
margin-left: 11px;
|
|
177
|
+
padding-left: 5px;
|
|
74
178
|
}
|
|
75
179
|
|
|
76
|
-
/* Pannello Superiore: Tabella Valori Attuali */
|
|
77
|
-
.summary-panel {
|
|
78
|
-
flex: 0 0 auto;
|
|
79
|
-
background: #1a1a1a;
|
|
80
|
-
border: 1px solid #333;
|
|
81
|
-
border-radius: 8px;
|
|
82
|
-
padding: 10px;
|
|
83
|
-
overflow-x: auto;
|
|
84
|
-
max-height: 250px;
|
|
85
|
-
overflow-y: auto;
|
|
86
|
-
}
|
|
87
180
|
table {
|
|
88
181
|
width: 100%;
|
|
89
182
|
border-collapse: collapse;
|
|
90
183
|
font-size: 13px;
|
|
91
|
-
table-layout: fixed; /* BLOCCO RIGIDO DELLE COLONNE
|
|
184
|
+
table-layout: fixed; /* BLOCCO RIGIDO DELLE COLONNE */
|
|
92
185
|
}
|
|
93
186
|
th, td {
|
|
94
187
|
border: 1px solid #333;
|
|
95
188
|
padding: 6px 8px;
|
|
96
189
|
text-align: left;
|
|
97
190
|
overflow: hidden;
|
|
98
|
-
word-wrap: break-word; /* Forza l'andamento a capo dei testi lunghi
|
|
191
|
+
word-wrap: break-word; /* Forza l'andamento a capo dei testi lunghi */
|
|
99
192
|
}
|
|
100
193
|
th { background-color: #222; color: #aaa; text-transform: uppercase; position: sticky; top: 0; z-index: 10; }
|
|
101
194
|
.val-cell { min-width: 120px; }
|
|
@@ -107,15 +200,15 @@
|
|
|
107
200
|
.time { color: #90caf9; font-size: 11px; }
|
|
108
201
|
.raw { color: #777; font-size: 10px; display: block; margin-top: 2px; }
|
|
109
202
|
|
|
110
|
-
/*
|
|
203
|
+
/* PANNELLO INFERIORE: Log Sequenziale in Formato Testo (Spazio flessibile rimanente) */
|
|
111
204
|
.log-panel {
|
|
112
|
-
flex: 1
|
|
205
|
+
flex-grow: 1; /* Occupa tutto lo spazio verticale rimanente della finestra */
|
|
113
206
|
background: #0a0a0a;
|
|
114
207
|
border: 1px solid #333;
|
|
115
208
|
border-radius: 8px;
|
|
116
209
|
display: flex;
|
|
117
210
|
flex-direction: column;
|
|
118
|
-
min-height:
|
|
211
|
+
min-height: 150px;
|
|
119
212
|
}
|
|
120
213
|
.log-header {
|
|
121
214
|
background: #222;
|
|
@@ -159,41 +252,50 @@
|
|
|
159
252
|
</head>
|
|
160
253
|
<body>
|
|
161
254
|
|
|
162
|
-
<h1>SignalK Data Debugger &
|
|
255
|
+
<h1>SignalK Data Debugger & CSV Dumper (Pro v6.0)</h1>
|
|
163
256
|
|
|
164
257
|
<div class="controls">
|
|
165
258
|
<label>Server IP:</label>
|
|
166
|
-
<input type="text" id="server-ip" value="
|
|
167
|
-
<button id="btn-
|
|
168
|
-
<
|
|
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>
|
|
169
263
|
</div>
|
|
170
264
|
|
|
171
|
-
<!--
|
|
172
|
-
<div class="
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
265
|
+
<!-- CONTENITORE PRINCIPALE DI LAVORO CON STRUTTURA FLUIDA ED ELEMENTI INTERATTIVI -->
|
|
266
|
+
<div class="workspace-grid" id="grid-container">
|
|
267
|
+
|
|
268
|
+
<!-- COLONNA SINISTRA: AREA FILTRI AVANZATA CON ALBERO GERARCHICO -->
|
|
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>
|
|
271
|
+
<div class="presets">
|
|
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>
|
|
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
|
+
|
|
281
|
+
<div id="path-trees-container" class="trees-container">
|
|
282
|
+
<!-- Categorie ed alberi gerarchici autogenerati dinamicamente via JS al termine della scansione -->
|
|
283
|
+
</div>
|
|
182
284
|
</div>
|
|
183
|
-
</div>
|
|
184
285
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
286
|
+
<!-- DIVISIONE ORIZZONTALE RIDIMENSIONABILE A PIACIMENTO (SPLITTER DRAG BAR) -->
|
|
287
|
+
<div class="splitter" id="drag-splitter"></div>
|
|
288
|
+
|
|
289
|
+
<!-- COLONNA DESTRA: PANNELLO TABELLA RIASSUNTIVA VALORI IN TEMPO REALE -->
|
|
290
|
+
<div class="summary-panel" id="pane-right">
|
|
189
291
|
<table>
|
|
190
292
|
<thead>
|
|
191
293
|
<tr>
|
|
192
294
|
<!-- BLOCCO RIGIDO DELLE LARGHEZZE DELLE COLONNE -->
|
|
193
|
-
<th width="
|
|
194
|
-
<th width="
|
|
195
|
-
<th width="
|
|
196
|
-
<th width="
|
|
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>
|
|
197
299
|
</tr>
|
|
198
300
|
</thead>
|
|
199
301
|
<tbody id="data-table">
|
|
@@ -202,56 +304,54 @@
|
|
|
202
304
|
</table>
|
|
203
305
|
</div>
|
|
204
306
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
</
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<!-- PANNELLO LOG SEQUENZIALE (CSV) - POSIZIONATO A TUTTA LARGHEZZA SUL FONDO -->
|
|
310
|
+
<div class="log-panel">
|
|
311
|
+
<div class="log-header">
|
|
312
|
+
<span>CSV Event Dump (Timestamp, Path, Value, Unit, Source, Spike)</span>
|
|
313
|
+
<div style="display: flex; align-items: center;">
|
|
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>
|
|
214
317
|
</div>
|
|
215
|
-
|
|
216
|
-
<textarea id="log-container" readonly></textarea>
|
|
217
318
|
</div>
|
|
218
|
-
|
|
319
|
+
|
|
320
|
+
<textarea id="log-container" readonly></textarea>
|
|
219
321
|
</div>
|
|
220
322
|
|
|
221
323
|
<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
|
-
];
|
|
324
|
+
// Registro dello stato dati e del database ad albero
|
|
325
|
+
const registeredPaths = new Set();
|
|
326
|
+
let pathTree = {}; // Albero gerarchico nidificato in memoria RAM
|
|
237
327
|
|
|
238
328
|
let socket = null;
|
|
239
329
|
let logLinesArray = [];
|
|
240
330
|
const MAX_LOG_LINES = 1000;
|
|
241
331
|
|
|
332
|
+
// Stato del sistema
|
|
333
|
+
let isScanning = false;
|
|
334
|
+
let isListening = false;
|
|
335
|
+
let scanTimer = null;
|
|
336
|
+
let scanCountdown = 10;
|
|
337
|
+
|
|
242
338
|
let lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
243
339
|
|
|
244
340
|
const radToDeg = (rad) => rad * (180 / Math.PI);
|
|
245
341
|
const msToKts = (ms) => ms * 1.94384;
|
|
246
342
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
343
|
+
const csvHeader = "Timestamp,Path,Value,Unit,Source,IsSpike";
|
|
344
|
+
document.getElementById('log-container').value = csvHeader + "\n";
|
|
345
|
+
|
|
346
|
+
// Inserisce e mappa dinamicamente un nuovo percorso nella tabella e nella struttura ad albero logica
|
|
347
|
+
function registerNewPath(path) {
|
|
348
|
+
if (registeredPaths.has(path)) return;
|
|
349
|
+
registeredPaths.add(path);
|
|
250
350
|
|
|
251
|
-
pathsToWatch.forEach(path => {
|
|
252
351
|
const safeId = path.replace(/\./g, '-');
|
|
253
352
|
|
|
254
|
-
// 1. Creazione
|
|
353
|
+
// 1. Creazione dinamica della riga nella tabella riassuntiva
|
|
354
|
+
const tbody = document.getElementById('data-table');
|
|
255
355
|
const tr = document.createElement('tr');
|
|
256
356
|
tr.id = `row-${safeId}`;
|
|
257
357
|
tr.innerHTML = `
|
|
@@ -262,24 +362,240 @@
|
|
|
262
362
|
`;
|
|
263
363
|
tbody.appendChild(tr);
|
|
264
364
|
|
|
265
|
-
// 2.
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
365
|
+
// 2. Costruzione della struttura gerarchica in memoria (RAM) per la generazione successiva
|
|
366
|
+
const parts = path.split('.');
|
|
367
|
+
let current = pathTree;
|
|
368
|
+
parts.forEach((part, index) => {
|
|
369
|
+
if (!current[part]) {
|
|
370
|
+
current[part] = {
|
|
371
|
+
_name: part,
|
|
372
|
+
_fullPath: parts.slice(0, index + 1).join('.'),
|
|
373
|
+
_isLeaf: (index === parts.length - 1),
|
|
374
|
+
_children: {}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
current = current[part]._children;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Disegna ricorsivamente l'albero gerarchico nel DOM (Lazy Rendering)
|
|
382
|
+
function renderTreeDOM(node, container, isRoot = false) {
|
|
383
|
+
const keys = Object.keys(node).sort();
|
|
384
|
+
|
|
385
|
+
keys.forEach(key => {
|
|
386
|
+
const item = node[key];
|
|
387
|
+
const hasChildren = Object.keys(item._children).length > 0;
|
|
388
|
+
|
|
389
|
+
const nodeDiv = document.createElement('div');
|
|
390
|
+
nodeDiv.className = `tree-node ${isRoot ? 'root-level' : ''} ${hasChildren ? 'tree-branch' : 'tree-leaf'}`;
|
|
391
|
+
|
|
392
|
+
const rowDiv = document.createElement('div');
|
|
393
|
+
rowDiv.className = 'node-row';
|
|
394
|
+
|
|
395
|
+
// 1. Pulsante Espandi/Collassa per i rami dotati di figli
|
|
396
|
+
if (hasChildren) {
|
|
397
|
+
const toggleSpan = document.createElement('span');
|
|
398
|
+
toggleSpan.className = 'node-toggle';
|
|
399
|
+
toggleSpan.innerText = '[+]'; // Compresso di default all'avvio
|
|
400
|
+
toggleSpan.onclick = (e) => {
|
|
401
|
+
e.stopPropagation();
|
|
402
|
+
const childContainer = nodeDiv.querySelector(':scope > .node-children');
|
|
403
|
+
if (childContainer.style.display === 'none') {
|
|
404
|
+
childContainer.style.display = 'flex';
|
|
405
|
+
toggleSpan.innerText = '[-]';
|
|
406
|
+
} else {
|
|
407
|
+
childContainer.style.display = 'none';
|
|
408
|
+
toggleSpan.innerText = '[+]';
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
rowDiv.appendChild(toggleSpan);
|
|
412
|
+
} else {
|
|
413
|
+
const spacer = document.createElement('span');
|
|
414
|
+
spacer.className = 'node-toggle';
|
|
415
|
+
rowDiv.appendChild(spacer);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 2. Checkbox di selezione
|
|
419
|
+
const chk = document.createElement('input');
|
|
420
|
+
chk.type = 'checkbox';
|
|
421
|
+
chk.checked = false; // Deselezionato di default per focalizzare lo spazio
|
|
422
|
+
chk.className = 'node-checkbox';
|
|
423
|
+
|
|
424
|
+
if (!hasChildren) {
|
|
425
|
+
const safeId = item._fullPath.replace(/\./g, '-');
|
|
426
|
+
chk.id = `chk-${safeId}`;
|
|
427
|
+
chk.dataset.path = item._fullPath;
|
|
428
|
+
} else {
|
|
429
|
+
const category = item._fullPath;
|
|
430
|
+
chk.id = `master-chk-${category.replace(/\./g, '-')}`;
|
|
431
|
+
chk.dataset.branchCategory = category;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
rowDiv.appendChild(chk);
|
|
435
|
+
|
|
436
|
+
// 3. Etichetta del Nodo
|
|
437
|
+
const labelSpan = document.createElement('span');
|
|
438
|
+
labelSpan.className = 'node-label';
|
|
439
|
+
labelSpan.innerText = key;
|
|
440
|
+
rowDiv.appendChild(labelSpan);
|
|
441
|
+
|
|
442
|
+
nodeDiv.appendChild(rowDiv);
|
|
443
|
+
|
|
444
|
+
// 4. Creazione ricorsiva dei nodi figli (Sotto-Albero nascosto di default all'avvio)
|
|
445
|
+
if (hasChildren) {
|
|
446
|
+
const childrenDiv = document.createElement('div');
|
|
447
|
+
childrenDiv.className = 'node-children';
|
|
448
|
+
childrenDiv.style.display = 'none'; // Chiuso di default all'avvio
|
|
449
|
+
renderTreeDOM(item._children, childrenDiv, false);
|
|
450
|
+
nodeDiv.appendChild(childrenDiv);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
container.appendChild(nodeDiv);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// DELEGAZIONE DEGLI EVENTI SUL CONTENITORE: Gestisce lo scorrimento e la propagazione degli stati
|
|
458
|
+
document.getElementById('path-trees-container').addEventListener('change', (e) => {
|
|
459
|
+
const chk = e.target;
|
|
460
|
+
if (!chk || chk.type !== 'checkbox') return;
|
|
461
|
+
|
|
462
|
+
// 1. PROPAGAZIONE VERSO IL BASSO: Se clicchi un nodo padre, spunta/disattiva tutti i suoi discendenti
|
|
463
|
+
const nodeDiv = chk.closest('.tree-node');
|
|
464
|
+
if (nodeDiv) {
|
|
465
|
+
const childCheckboxes = nodeDiv.querySelectorAll('.node-checkbox');
|
|
466
|
+
childCheckboxes.forEach(c => {
|
|
467
|
+
c.checked = chk.checked;
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 2. CASCATA VERSO L'ALTO: Ricalcola gli stati dei padri (incluso lo stato indeterminato "-")
|
|
472
|
+
updateAllTreeMasterCheckboxes();
|
|
473
|
+
|
|
474
|
+
// 3. FILTRAGGIO DINAMICO DELLA TABELLA: Aggiorna istantaneamente la visibilità dei valori
|
|
475
|
+
updateTableVisibility();
|
|
476
|
+
|
|
477
|
+
// 4. FILTRAGGIO DINAMICO DEL DUMP CSV: Aggiorna lo schermo dei log al cambio di selezione
|
|
478
|
+
renderLogTextArea();
|
|
273
479
|
});
|
|
274
480
|
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
550
|
+
function updateTableVisibility() {
|
|
551
|
+
const queryInput = document.getElementById('tree-search');
|
|
552
|
+
const q = queryInput ? queryInput.value.toLowerCase().trim() : "";
|
|
553
|
+
|
|
554
|
+
registeredPaths.forEach(path => {
|
|
555
|
+
const safeId = path.replace(/\./g, '-');
|
|
556
|
+
const chk = document.getElementById(`chk-${safeId}`);
|
|
557
|
+
const row = document.getElementById(`row-${safeId}`);
|
|
558
|
+
|
|
559
|
+
if (row) {
|
|
560
|
+
if (isScanning) {
|
|
561
|
+
// Durante la scansione mostriamo tutte le righe scoperte in tempo reale
|
|
562
|
+
row.style.display = '';
|
|
563
|
+
} else {
|
|
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
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Ricalcola ricorsivamente gli stati (Checked, Unchecked, Indeterminate) dal basso verso l'alto
|
|
579
|
+
function updateAllTreeMasterCheckboxes() {
|
|
580
|
+
const branchNodes = Array.from(document.querySelectorAll('.tree-branch')).reverse();
|
|
581
|
+
|
|
582
|
+
branchNodes.forEach(branchNode => {
|
|
583
|
+
const masterCheckbox = branchNode.querySelector(':scope > .node-row > .node-checkbox');
|
|
584
|
+
if (!masterCheckbox) return;
|
|
585
|
+
|
|
586
|
+
const childCheckboxes = branchNode.querySelectorAll('.tree-leaf input[type="checkbox"]');
|
|
587
|
+
const checkedCount = Array.from(childCheckboxes).filter(c => c.checked).length;
|
|
588
|
+
|
|
589
|
+
masterCheckbox.checked = (checkedCount === childCheckboxes.length);
|
|
590
|
+
masterCheckbox.indeterminate = (checkedCount > 0 && checkedCount < childCheckboxes.length);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
277
593
|
|
|
278
594
|
// =========================================================
|
|
279
|
-
// PRESET RAPIDI DI SELEZIONE
|
|
595
|
+
// PRESET RAPIDI DI SELEZIONE (Mappati sulle foglie attive)
|
|
280
596
|
// =========================================================
|
|
281
597
|
function applyPreset(preset) {
|
|
282
|
-
|
|
598
|
+
registeredPaths.forEach(path => {
|
|
283
599
|
const safeId = path.replace(/\./g, '-');
|
|
284
600
|
const chk = document.getElementById(`chk-${safeId}`);
|
|
285
601
|
if (!chk) return;
|
|
@@ -289,15 +605,16 @@
|
|
|
289
605
|
} else if (preset === 'none') {
|
|
290
606
|
chk.checked = false;
|
|
291
607
|
} else if (preset === 'gps') {
|
|
292
|
-
// Solo GPS, SOG e COG
|
|
293
608
|
const isGps = path.includes('position') || path === 'navigation.speedOverGround' || path === 'navigation.courseOverGroundTrue';
|
|
294
609
|
chk.checked = isGps;
|
|
295
610
|
} else if (preset === 'twd') {
|
|
296
|
-
|
|
297
|
-
const isTwd = path.includes('wind') || path === 'navigation.headingTrue' || path === 'navigation.courseOverGroundTrue';
|
|
611
|
+
const isTwd = path.includes('wind') || path === 'navigation.headingTrue' || path === 'navigation.courseOverGroundTrue' || path === 'navigation.headingMagnetic';
|
|
298
612
|
chk.checked = isTwd;
|
|
299
613
|
}
|
|
300
614
|
});
|
|
615
|
+
updateAllTreeMasterCheckboxes();
|
|
616
|
+
updateTableVisibility(); // Sincronizza la tabella visiva al cambio di preset
|
|
617
|
+
renderLogTextArea(); // Filtra dinamicamente anche i log CSV in base al preset!
|
|
301
618
|
}
|
|
302
619
|
|
|
303
620
|
// =========================================================
|
|
@@ -318,41 +635,148 @@
|
|
|
318
635
|
}
|
|
319
636
|
|
|
320
637
|
// =========================================================
|
|
321
|
-
//
|
|
638
|
+
// FASE 1: AVVIO SCANSIONE DI SCOPERTA (DISCOVERY - 10s)
|
|
322
639
|
// =========================================================
|
|
323
|
-
function
|
|
324
|
-
|
|
640
|
+
function startScan() {
|
|
641
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
642
|
+
socket.close();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Pulisce l'albero logico e l'interfaccia prima del nuovo avvio
|
|
646
|
+
registeredPaths.clear();
|
|
647
|
+
pathTree = {};
|
|
648
|
+
document.getElementById('path-trees-container').innerHTML = '';
|
|
649
|
+
document.getElementById('data-table').innerHTML = '';
|
|
650
|
+
document.getElementById('tree-search').value = ''; // Resetta il testo della barra di ricerca
|
|
651
|
+
|
|
652
|
+
const btnScan = document.getElementById('btn-scan');
|
|
653
|
+
const btnListen = document.getElementById('btn-listen');
|
|
325
654
|
const status = document.getElementById('status');
|
|
326
655
|
const ip = document.getElementById('server-ip').value;
|
|
327
656
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
657
|
+
isScanning = true;
|
|
658
|
+
isListening = false;
|
|
659
|
+
scanCountdown = 10;
|
|
660
|
+
|
|
661
|
+
btnScan.disabled = true;
|
|
662
|
+
btnScan.innerText = `Scanning... ${scanCountdown}s`;
|
|
663
|
+
btnListen.disabled = true;
|
|
664
|
+
btnListen.className = "secondary";
|
|
665
|
+
btnListen.innerText = "Listen";
|
|
666
|
+
|
|
667
|
+
status.innerText = "SCAN IN PROGRESS";
|
|
668
|
+
status.className = "scanning";
|
|
669
|
+
|
|
670
|
+
socket = new WebSocket(`ws://${ip}/signalk/v1/stream?subscribe=self`);
|
|
671
|
+
|
|
672
|
+
socket.onopen = () => {
|
|
673
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Scan Phase Started", "-", ip, false);
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
scanTimer = setInterval(() => {
|
|
677
|
+
scanCountdown--;
|
|
678
|
+
if (scanCountdown > 0) {
|
|
679
|
+
btnScan.innerText = `Scanning... ${scanCountdown}s`;
|
|
680
|
+
} else {
|
|
681
|
+
// Scansione terminata! Generazione ricorsiva dell'albero gerarchico reale
|
|
682
|
+
clearInterval(scanTimer);
|
|
683
|
+
scanTimer = null;
|
|
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)
|
|
687
|
+
|
|
688
|
+
const treeContainer = document.getElementById('path-trees-container');
|
|
689
|
+
renderTreeDOM(pathTree, treeContainer, true); // Genera ricorsivamente l'albero
|
|
690
|
+
updateAllTreeMasterCheckboxes(); // Allinea gli stati dei padri
|
|
691
|
+
updateTableVisibility(); // Inizializza i filtri della tabella (Nasconde tutto all'avvio)
|
|
692
|
+
|
|
693
|
+
btnScan.disabled = false;
|
|
694
|
+
btnScan.innerText = "Scan Network (10s)";
|
|
695
|
+
|
|
696
|
+
btnListen.disabled = false;
|
|
697
|
+
btnListen.className = "";
|
|
698
|
+
|
|
699
|
+
status.innerText = "SCAN COMPLETED";
|
|
700
|
+
status.className = "online";
|
|
701
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Scan Phase Finished", "-", "-", false);
|
|
702
|
+
}
|
|
703
|
+
}, 1000);
|
|
704
|
+
|
|
705
|
+
socket.onmessage = (event) => {
|
|
706
|
+
try {
|
|
707
|
+
const data = JSON.parse(event.data);
|
|
708
|
+
if (data.updates) {
|
|
709
|
+
data.updates.forEach(update => {
|
|
710
|
+
const sourceName = extractSourceIdentifier(update);
|
|
711
|
+
const timeISO = new Date(update.timestamp).toISOString();
|
|
712
|
+
const timeUI = new Date(update.timestamp).toLocaleTimeString() + '.' + new Date(update.timestamp).getMilliseconds().toString().padStart(3, '0');
|
|
713
|
+
|
|
714
|
+
if (update.values) {
|
|
715
|
+
update.values.forEach(v => {
|
|
716
|
+
// Popola e accumula i percorsi in memoria ad albero
|
|
717
|
+
registerNewPath(v.path);
|
|
718
|
+
processData(v.path, v.value, sourceName, timeISO, timeUI, false); // Nessun log scritto
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
} catch (err) {
|
|
724
|
+
console.error("Errore scansione:", err);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
socket.onclose = () => {
|
|
729
|
+
if (isScanning) {
|
|
730
|
+
clearInterval(scanTimer);
|
|
731
|
+
isScanning = false;
|
|
732
|
+
btnScan.disabled = false;
|
|
733
|
+
btnScan.innerText = "Scan Network (10s)";
|
|
734
|
+
status.innerText = "SCAN FAILED";
|
|
735
|
+
status.className = "";
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// =========================================================
|
|
741
|
+
// FASE 2: ASCOLTO SELETTIVO (LOG ATTIVI SOLO SE SPUNTATI)
|
|
742
|
+
// =========================================================
|
|
743
|
+
function toggleListening() {
|
|
744
|
+
const btnListen = document.getElementById('btn-listen');
|
|
745
|
+
const btnScan = document.getElementById('btn-scan');
|
|
746
|
+
const status = document.getElementById('status');
|
|
747
|
+
const ip = document.getElementById('server-ip').value;
|
|
748
|
+
|
|
749
|
+
if (isListening) {
|
|
750
|
+
if (socket) socket.close();
|
|
751
|
+
isListening = false;
|
|
752
|
+
btnListen.innerText = "Listen";
|
|
753
|
+
btnListen.className = "";
|
|
754
|
+
btnScan.disabled = false;
|
|
755
|
+
status.innerText = "LISTENING STOPPED";
|
|
756
|
+
status.className = "online";
|
|
757
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Listening Stopped", "-", "-", false);
|
|
334
758
|
return;
|
|
335
759
|
}
|
|
336
760
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
761
|
+
isListening = true;
|
|
762
|
+
btnScan.disabled = true;
|
|
763
|
+
btnListen.innerText = "Stop Dump";
|
|
764
|
+
btnListen.className = "stop";
|
|
765
|
+
|
|
766
|
+
status.innerText = "DUMPING VALUES...";
|
|
767
|
+
status.className = "online";
|
|
341
768
|
|
|
342
769
|
lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
343
770
|
|
|
344
771
|
socket = new WebSocket(`ws://${ip}/signalk/v1/stream?subscribe=self`);
|
|
345
772
|
|
|
346
773
|
socket.onopen = () => {
|
|
347
|
-
|
|
348
|
-
status.className = "online";
|
|
349
|
-
addLogEntry(new Date().toISOString(), "SYSTEM", "Connected", "-", ip, false);
|
|
774
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Selective Listening Started", "-", ip, false);
|
|
350
775
|
};
|
|
351
776
|
|
|
352
777
|
socket.onmessage = (event) => {
|
|
353
778
|
try {
|
|
354
779
|
const data = JSON.parse(event.data);
|
|
355
|
-
|
|
356
780
|
if (data.updates) {
|
|
357
781
|
data.updates.forEach(update => {
|
|
358
782
|
const sourceName = extractSourceIdentifier(update);
|
|
@@ -361,36 +785,58 @@
|
|
|
361
785
|
|
|
362
786
|
if (update.values) {
|
|
363
787
|
update.values.forEach(v => {
|
|
364
|
-
|
|
365
|
-
|
|
788
|
+
const safeId = v.path.replace(/\./g, '-');
|
|
789
|
+
const chk = document.getElementById(`chk-${safeId}`);
|
|
790
|
+
|
|
791
|
+
// Verifica sia la spunta sia l'effettiva visibilità a schermo (non nascosto da ricerca!)
|
|
792
|
+
let isLogged = false;
|
|
793
|
+
if (chk && chk.checked) {
|
|
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!
|
|
802
|
+
processData(v.path, v.value, sourceName, timeISO, timeUI, true);
|
|
803
|
+
} else {
|
|
804
|
+
// Se non è spuntata o è nascosta, aggiorna passivamente la tabella silenziando i log
|
|
805
|
+
if (registeredPaths.has(v.path)) {
|
|
806
|
+
processData(v.path, v.value, sourceName, timeISO, timeUI, false);
|
|
807
|
+
}
|
|
366
808
|
}
|
|
367
809
|
});
|
|
368
810
|
}
|
|
369
811
|
});
|
|
370
812
|
}
|
|
371
813
|
} catch (err) {
|
|
372
|
-
console.error("Errore
|
|
814
|
+
console.error("Errore ascolto:", err);
|
|
373
815
|
}
|
|
374
816
|
};
|
|
375
817
|
|
|
376
818
|
socket.onclose = () => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
819
|
+
if (isListening) {
|
|
820
|
+
isListening = false;
|
|
821
|
+
btnListen.innerText = "Listen";
|
|
822
|
+
btnListen.className = "";
|
|
823
|
+
btnScan.disabled = false;
|
|
824
|
+
status.innerText = "DISCONNESSO";
|
|
825
|
+
status.className = "";
|
|
826
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Connection Closed", "-", "-", false);
|
|
827
|
+
}
|
|
382
828
|
};
|
|
383
|
-
|
|
384
|
-
socket.onerror = (
|
|
385
|
-
status.innerText = "
|
|
386
|
-
addLogEntry(new Date().toISOString(), "SYSTEM", "Error", "-", "-", true);
|
|
829
|
+
|
|
830
|
+
socket.onerror = () => {
|
|
831
|
+
status.innerText = "CONNECTION ERROR";
|
|
832
|
+
addLogEntry(new Date().toISOString(), "SYSTEM", "Connection Error", "-", "-", true);
|
|
387
833
|
};
|
|
388
834
|
}
|
|
389
835
|
|
|
390
836
|
// =========================================================
|
|
391
837
|
// ELABORAZIONE DATI E SPIKE DETECTION
|
|
392
838
|
// =========================================================
|
|
393
|
-
function processData(path, rawValue, source, timeISO, timeUI) {
|
|
839
|
+
function processData(path, rawValue, source, timeISO, timeUI, writeToCsv) {
|
|
394
840
|
let formattedVal = "---";
|
|
395
841
|
let unit = "";
|
|
396
842
|
let isSpike = false;
|
|
@@ -398,7 +844,7 @@
|
|
|
398
844
|
if (rawValue !== null && rawValue !== undefined) {
|
|
399
845
|
if (path === "navigation.position") {
|
|
400
846
|
if (typeof rawValue === 'object' && rawValue.latitude !== undefined && rawValue.longitude !== undefined) {
|
|
401
|
-
formattedVal = `${rawValue.latitude.toFixed(6)}
|
|
847
|
+
formattedVal = `${rawValue.latitude.toFixed(6)}`;
|
|
402
848
|
unit = "lat;lon";
|
|
403
849
|
} else {
|
|
404
850
|
formattedVal = "Invalid Position";
|
|
@@ -439,10 +885,8 @@
|
|
|
439
885
|
// 1. Aggiorna la Tabella in alto
|
|
440
886
|
updateTableRow(path, formattedVal, unit, rawValue, source, timeUI);
|
|
441
887
|
|
|
442
|
-
// 2. Aggiungi riga al Log CSV
|
|
443
|
-
|
|
444
|
-
const chk = document.getElementById(`chk-${safeId}`);
|
|
445
|
-
if (chk && chk.checked) {
|
|
888
|
+
// 2. Aggiungi riga al Log CSV
|
|
889
|
+
if (writeToCsv) {
|
|
446
890
|
addLogEntry(timeISO, path, formattedVal, unit, source, isSpike);
|
|
447
891
|
}
|
|
448
892
|
}
|
|
@@ -471,7 +915,7 @@
|
|
|
471
915
|
srcCell.innerHTML = `<span class="source">${source}</span>`;
|
|
472
916
|
}
|
|
473
917
|
|
|
474
|
-
const timeCell = document.getElementById(
|
|
918
|
+
const timeCell = document.getElementById('time-' + safeId);
|
|
475
919
|
if (timeCell) {
|
|
476
920
|
timeCell.innerHTML = `<span class="time">${timeUI}</span>`;
|
|
477
921
|
}
|
|
@@ -502,9 +946,28 @@
|
|
|
502
946
|
renderLogTextArea();
|
|
503
947
|
}
|
|
504
948
|
|
|
949
|
+
// Filtra ricorsivamente i log visualizzati nel Dump CSV in tempo reale in base alla selezione attiva e visibile
|
|
505
950
|
function renderLogTextArea() {
|
|
506
951
|
const textArea = document.getElementById('log-container');
|
|
507
|
-
|
|
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");
|
|
508
971
|
textArea.scrollTop = textArea.scrollHeight;
|
|
509
972
|
}
|
|
510
973
|
|
|
@@ -527,7 +990,7 @@
|
|
|
527
990
|
}
|
|
528
991
|
} catch (err) {
|
|
529
992
|
console.error("Copia fallita: ", err);
|
|
530
|
-
alert("
|
|
993
|
+
alert("Error copying to clipboard. Please use CTRL+C / CMD+C manually.");
|
|
531
994
|
}
|
|
532
995
|
}
|
|
533
996
|
|
|
@@ -537,6 +1000,38 @@
|
|
|
537
1000
|
setTimeout(() => notif.style.opacity = 0, 2000);
|
|
538
1001
|
window.getSelection().removeAllRanges();
|
|
539
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
|
+
});
|
|
540
1035
|
</script>
|
|
541
1036
|
</body>
|
|
542
1037
|
</html>
|