@sailingrotevista/rotevista-dash 7.0.45 → 8.0.2
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.
Potentially problematic release.
This version of @sailingrotevista/rotevista-dash might be problematic. Click here for more details.
- package/README.md +28 -70
- package/index.js +14 -0
- package/package.json +26 -1
- package/public/assets/dash-DmyxO1aO.png +0 -0
- package/public/assets/index-BFSy7dOW.css +1 -0
- package/public/assets/index-BHhAwNqq.js +9 -0
- package/public/assets/manifest-Ea_0WKQ8.json +17 -0
- package/public/icons/dash.png +0 -0
- package/public/index.html +23 -0
- package/public/manifest.json +17 -0
- package/app.js +0 -1505
- package/charts.js +0 -305
- package/debug.html +0 -1037
- package/gauge.js +0 -90
- package/index.html +0 -264
- package/style.css +0 -640
- package/utils.js +0 -148
- package/weather-radar.js +0 -305
- package/weather_debug.html +0 -181
package/debug.html
DELETED
|
@@ -1,1037 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>SignalK Data Debugger & CSV Dumper (Pro v6.0)</title>
|
|
7
|
-
<style>
|
|
8
|
-
body {
|
|
9
|
-
background-color: #121212;
|
|
10
|
-
color: #eee;
|
|
11
|
-
font-family: monospace;
|
|
12
|
-
margin: 0;
|
|
13
|
-
padding: 20px;
|
|
14
|
-
display: flex;
|
|
15
|
-
flex-direction: column;
|
|
16
|
-
height: 100vh;
|
|
17
|
-
box-sizing: border-box;
|
|
18
|
-
}
|
|
19
|
-
h1 { color: #4CAF50; border-bottom: 1px solid #333; padding-bottom: 10px; margin-top: 0; flex-shrink: 0; }
|
|
20
|
-
|
|
21
|
-
/* Controlli in alto */
|
|
22
|
-
.controls { margin-bottom: 15px; display: flex; gap: 10px; align-items: center; flex-shrink: 0; flex-wrap: wrap; }
|
|
23
|
-
input[type="text"] { padding: 8px; background: #222; color: #fff; border: 1px solid #444; width: 200px; }
|
|
24
|
-
button { padding: 8px 15px; background: #4CAF50; color: white; border: none; cursor: pointer; font-weight: bold; border-radius: 4px; }
|
|
25
|
-
button:hover { opacity: 0.8; }
|
|
26
|
-
button.stop { background: #f44336; }
|
|
27
|
-
button.secondary { background: #555; }
|
|
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
|
-
|
|
37
|
-
#status { font-weight: bold; color: #f44336; margin-left: auto; }
|
|
38
|
-
#status.online { color: #4CAF50; }
|
|
39
|
-
#status.scanning { color: #ff9800; }
|
|
40
|
-
|
|
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 */
|
|
52
|
-
.filter-container {
|
|
53
|
-
background: #1a1a1a;
|
|
54
|
-
border: 1px solid #333;
|
|
55
|
-
border-radius: 8px;
|
|
56
|
-
padding: 15px;
|
|
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 */
|
|
65
|
-
}
|
|
66
|
-
.presets {
|
|
67
|
-
margin: 10px 0;
|
|
68
|
-
display: flex;
|
|
69
|
-
gap: 10px;
|
|
70
|
-
flex-wrap: wrap;
|
|
71
|
-
flex-shrink: 0;
|
|
72
|
-
}
|
|
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;
|
|
124
|
-
border-top: 1px solid #222;
|
|
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 */
|
|
129
|
-
}
|
|
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 {
|
|
144
|
-
display: flex;
|
|
145
|
-
align-items: center;
|
|
146
|
-
gap: 8px;
|
|
147
|
-
padding: 6px 10px;
|
|
148
|
-
font-size: 12px;
|
|
149
|
-
user-select: none;
|
|
150
|
-
}
|
|
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 {
|
|
158
|
-
cursor: pointer;
|
|
159
|
-
color: #888;
|
|
160
|
-
font-weight: bold;
|
|
161
|
-
width: 24px;
|
|
162
|
-
text-align: center;
|
|
163
|
-
font-size: 11px;
|
|
164
|
-
}
|
|
165
|
-
.node-toggle:hover {
|
|
166
|
-
color: #4CAF50;
|
|
167
|
-
}
|
|
168
|
-
.node-label {
|
|
169
|
-
color: #ccc;
|
|
170
|
-
font-size: 11px;
|
|
171
|
-
}
|
|
172
|
-
.node-children {
|
|
173
|
-
display: flex;
|
|
174
|
-
flex-direction: column;
|
|
175
|
-
border-left: 1px dashed #2c2c2c;
|
|
176
|
-
margin-left: 11px;
|
|
177
|
-
padding-left: 5px;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
table {
|
|
181
|
-
width: 100%;
|
|
182
|
-
border-collapse: collapse;
|
|
183
|
-
font-size: 13px;
|
|
184
|
-
table-layout: fixed; /* BLOCCO RIGIDO DELLE COLONNE */
|
|
185
|
-
}
|
|
186
|
-
th, td {
|
|
187
|
-
border: 1px solid #333;
|
|
188
|
-
padding: 6px 8px;
|
|
189
|
-
text-align: left;
|
|
190
|
-
overflow: hidden;
|
|
191
|
-
word-wrap: break-word; /* Forza l'andamento a capo dei testi lunghi */
|
|
192
|
-
}
|
|
193
|
-
th { background-color: #222; color: #aaa; text-transform: uppercase; position: sticky; top: 0; z-index: 10; }
|
|
194
|
-
.val-cell { min-width: 120px; }
|
|
195
|
-
|
|
196
|
-
/* Stili Testo Tabella */
|
|
197
|
-
.value { color: #64ffda; font-weight: bold; font-size: 15px; }
|
|
198
|
-
.unit { color: #888; font-size: 11px; margin-left: 5px; }
|
|
199
|
-
.source { color: #ffb74d; font-size: 11px; word-break: break-all; }
|
|
200
|
-
.time { color: #90caf9; font-size: 11px; }
|
|
201
|
-
.raw { color: #777; font-size: 10px; display: block; margin-top: 2px; }
|
|
202
|
-
|
|
203
|
-
/* PANNELLO INFERIORE: Log Sequenziale in Formato Testo (Spazio flessibile rimanente) */
|
|
204
|
-
.log-panel {
|
|
205
|
-
flex-grow: 1; /* Occupa tutto lo spazio verticale rimanente della finestra */
|
|
206
|
-
background: #0a0a0a;
|
|
207
|
-
border: 1px solid #333;
|
|
208
|
-
border-radius: 8px;
|
|
209
|
-
display: flex;
|
|
210
|
-
flex-direction: column;
|
|
211
|
-
min-height: 150px;
|
|
212
|
-
}
|
|
213
|
-
.log-header {
|
|
214
|
-
background: #222;
|
|
215
|
-
padding: 8px 15px;
|
|
216
|
-
border-bottom: 1px solid #333;
|
|
217
|
-
display: flex;
|
|
218
|
-
justify-content: space-between;
|
|
219
|
-
align-items: center;
|
|
220
|
-
border-radius: 8px 8px 0 0;
|
|
221
|
-
font-size: 13px;
|
|
222
|
-
font-weight: bold;
|
|
223
|
-
color: #aaa;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/* Area di testo del Log */
|
|
227
|
-
textarea#log-container {
|
|
228
|
-
flex-grow: 1;
|
|
229
|
-
background: transparent;
|
|
230
|
-
color: #ddd;
|
|
231
|
-
border: none;
|
|
232
|
-
padding: 10px;
|
|
233
|
-
font-family: monospace;
|
|
234
|
-
font-size: 12px;
|
|
235
|
-
line-height: 1.4;
|
|
236
|
-
resize: none;
|
|
237
|
-
outline: none;
|
|
238
|
-
white-space: pre;
|
|
239
|
-
overflow-wrap: normal;
|
|
240
|
-
overflow-x: auto;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/* Notifica di Copia Riuscita */
|
|
244
|
-
#copy-notification {
|
|
245
|
-
color: #4CAF50;
|
|
246
|
-
font-size: 12px;
|
|
247
|
-
margin-right: 10px;
|
|
248
|
-
opacity: 0;
|
|
249
|
-
transition: opacity 0.5s;
|
|
250
|
-
}
|
|
251
|
-
</style>
|
|
252
|
-
</head>
|
|
253
|
-
<body>
|
|
254
|
-
|
|
255
|
-
<h1>SignalK Data Debugger & CSV Dumper (Pro v6.0)</h1>
|
|
256
|
-
|
|
257
|
-
<div class="controls">
|
|
258
|
-
<label>Server IP:</label>
|
|
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>
|
|
263
|
-
</div>
|
|
264
|
-
|
|
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>
|
|
284
|
-
</div>
|
|
285
|
-
|
|
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">
|
|
291
|
-
<table>
|
|
292
|
-
<thead>
|
|
293
|
-
<tr>
|
|
294
|
-
<!-- BLOCCO RIGIDO DELLE LARGHEZZE DELLE COLONNE -->
|
|
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>
|
|
299
|
-
</tr>
|
|
300
|
-
</thead>
|
|
301
|
-
<tbody id="data-table">
|
|
302
|
-
<!-- Generate via JS -->
|
|
303
|
-
</tbody>
|
|
304
|
-
</table>
|
|
305
|
-
</div>
|
|
306
|
-
|
|
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>
|
|
317
|
-
</div>
|
|
318
|
-
</div>
|
|
319
|
-
|
|
320
|
-
<textarea id="log-container" readonly></textarea>
|
|
321
|
-
</div>
|
|
322
|
-
|
|
323
|
-
<script>
|
|
324
|
-
// Registro dello stato dati e del database ad albero
|
|
325
|
-
const registeredPaths = new Set();
|
|
326
|
-
let pathTree = {}; // Albero gerarchico nidificato in memoria RAM
|
|
327
|
-
|
|
328
|
-
let socket = null;
|
|
329
|
-
let logLinesArray = [];
|
|
330
|
-
const MAX_LOG_LINES = 1000;
|
|
331
|
-
|
|
332
|
-
// Stato del sistema
|
|
333
|
-
let isScanning = false;
|
|
334
|
-
let isListening = false;
|
|
335
|
-
let scanTimer = null;
|
|
336
|
-
let scanCountdown = 10;
|
|
337
|
-
|
|
338
|
-
let lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
339
|
-
|
|
340
|
-
const radToDeg = (rad) => rad * (180 / Math.PI);
|
|
341
|
-
const msToKts = (ms) => ms * 1.94384;
|
|
342
|
-
|
|
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);
|
|
350
|
-
|
|
351
|
-
const safeId = path.replace(/\./g, '-');
|
|
352
|
-
|
|
353
|
-
// 1. Creazione dinamica della riga nella tabella riassuntiva
|
|
354
|
-
const tbody = document.getElementById('data-table');
|
|
355
|
-
const tr = document.createElement('tr');
|
|
356
|
-
tr.id = `row-${safeId}`;
|
|
357
|
-
tr.innerHTML = `
|
|
358
|
-
<td><strong>${path}</strong></td>
|
|
359
|
-
<td id="val-${safeId}" class="val-cell"><span class="value">---</span></td>
|
|
360
|
-
<td id="src-${safeId}"><span class="source">---</span></td>
|
|
361
|
-
<td id="time-${safeId}"><span class="time">---</span></td>
|
|
362
|
-
`;
|
|
363
|
-
tbody.appendChild(tr);
|
|
364
|
-
|
|
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();
|
|
479
|
-
});
|
|
480
|
-
|
|
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
|
-
}
|
|
593
|
-
|
|
594
|
-
// =========================================================
|
|
595
|
-
// PRESET RAPIDI DI SELEZIONE (Mappati sulle foglie attive)
|
|
596
|
-
// =========================================================
|
|
597
|
-
function applyPreset(preset) {
|
|
598
|
-
registeredPaths.forEach(path => {
|
|
599
|
-
const safeId = path.replace(/\./g, '-');
|
|
600
|
-
const chk = document.getElementById(`chk-${safeId}`);
|
|
601
|
-
if (!chk) return;
|
|
602
|
-
|
|
603
|
-
if (preset === 'all') {
|
|
604
|
-
chk.checked = true;
|
|
605
|
-
} else if (preset === 'none') {
|
|
606
|
-
chk.checked = false;
|
|
607
|
-
} else if (preset === 'gps') {
|
|
608
|
-
const isGps = path.includes('position') || path === 'navigation.speedOverGround' || path === 'navigation.courseOverGroundTrue';
|
|
609
|
-
chk.checked = isGps;
|
|
610
|
-
} else if (preset === 'twd') {
|
|
611
|
-
const isTwd = path.includes('wind') || path === 'navigation.headingTrue' || path === 'navigation.courseOverGroundTrue' || path === 'navigation.headingMagnetic';
|
|
612
|
-
chk.checked = isTwd;
|
|
613
|
-
}
|
|
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!
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// =========================================================
|
|
621
|
-
// ESTRAZIONE AVANZATA DELLA SORGENTE (GPS / NMEA)
|
|
622
|
-
// =========================================================
|
|
623
|
-
function extractSourceIdentifier(update) {
|
|
624
|
-
if (update.$source) return update.$source;
|
|
625
|
-
if (update.source) {
|
|
626
|
-
if (typeof update.source === 'object') {
|
|
627
|
-
return update.source.label ||
|
|
628
|
-
update.source.talker ||
|
|
629
|
-
update.source.src ||
|
|
630
|
-
(update.source.device ? `${update.source.device}:${update.source.pgn || ''}` : JSON.stringify(update.source));
|
|
631
|
-
}
|
|
632
|
-
return String(update.source);
|
|
633
|
-
}
|
|
634
|
-
return "Unknown-Source";
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// =========================================================
|
|
638
|
-
// FASE 1: AVVIO SCANSIONE DI SCOPERTA (DISCOVERY - 10s)
|
|
639
|
-
// =========================================================
|
|
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');
|
|
654
|
-
const status = document.getElementById('status');
|
|
655
|
-
const ip = document.getElementById('server-ip').value;
|
|
656
|
-
|
|
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);
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
|
|
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";
|
|
768
|
-
|
|
769
|
-
lastValues = { "navigation.speedOverGround": 0, "navigation.courseOverGroundTrue": 0 };
|
|
770
|
-
|
|
771
|
-
socket = new WebSocket(`ws://${ip}/signalk/v1/stream?subscribe=self`);
|
|
772
|
-
|
|
773
|
-
socket.onopen = () => {
|
|
774
|
-
addLogEntry(new Date().toISOString(), "SYSTEM", "Selective Listening Started", "-", ip, false);
|
|
775
|
-
};
|
|
776
|
-
|
|
777
|
-
socket.onmessage = (event) => {
|
|
778
|
-
try {
|
|
779
|
-
const data = JSON.parse(event.data);
|
|
780
|
-
if (data.updates) {
|
|
781
|
-
data.updates.forEach(update => {
|
|
782
|
-
const sourceName = extractSourceIdentifier(update);
|
|
783
|
-
const timeISO = new Date(update.timestamp).toISOString();
|
|
784
|
-
const timeUI = new Date(update.timestamp).toLocaleTimeString() + '.' + new Date(update.timestamp).getMilliseconds().toString().padStart(3, '0');
|
|
785
|
-
|
|
786
|
-
if (update.values) {
|
|
787
|
-
update.values.forEach(v => {
|
|
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
|
-
}
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
} catch (err) {
|
|
814
|
-
console.error("Errore ascolto:", err);
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
socket.onclose = () => {
|
|
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
|
-
}
|
|
828
|
-
};
|
|
829
|
-
|
|
830
|
-
socket.onerror = () => {
|
|
831
|
-
status.innerText = "CONNECTION ERROR";
|
|
832
|
-
addLogEntry(new Date().toISOString(), "SYSTEM", "Connection Error", "-", "-", true);
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// =========================================================
|
|
837
|
-
// ELABORAZIONE DATI E SPIKE DETECTION
|
|
838
|
-
// =========================================================
|
|
839
|
-
function processData(path, rawValue, source, timeISO, timeUI, writeToCsv) {
|
|
840
|
-
let formattedVal = "---";
|
|
841
|
-
let unit = "";
|
|
842
|
-
let isSpike = false;
|
|
843
|
-
|
|
844
|
-
if (rawValue !== null && rawValue !== undefined) {
|
|
845
|
-
if (path === "navigation.position") {
|
|
846
|
-
if (typeof rawValue === 'object' && rawValue.latitude !== undefined && rawValue.longitude !== undefined) {
|
|
847
|
-
formattedVal = `${rawValue.latitude.toFixed(6)}`;
|
|
848
|
-
unit = "lat;lon";
|
|
849
|
-
} else {
|
|
850
|
-
formattedVal = "Invalid Position";
|
|
851
|
-
}
|
|
852
|
-
} else if (path.includes("speed")) {
|
|
853
|
-
formattedVal = msToKts(rawValue).toFixed(3);
|
|
854
|
-
unit = "kts";
|
|
855
|
-
|
|
856
|
-
if (path === "navigation.speedOverGround") {
|
|
857
|
-
let diff = Math.abs(formattedVal - lastValues[path]);
|
|
858
|
-
if (diff > 5.0 && lastValues[path] > 0) isSpike = true;
|
|
859
|
-
lastValues[path] = formattedVal;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
} else if (path.includes("angle") || path.includes("heading") || path.includes("course") || path.includes("direction")) {
|
|
863
|
-
formattedVal = radToDeg(rawValue).toFixed(2);
|
|
864
|
-
unit = "deg";
|
|
865
|
-
|
|
866
|
-
if (path === "navigation.courseOverGroundTrue") {
|
|
867
|
-
let diff = Math.abs(formattedVal - lastValues[path]);
|
|
868
|
-
if (diff > 180) diff = 360 - diff;
|
|
869
|
-
if (diff > 45 && lastValues[path] > 0) isSpike = true;
|
|
870
|
-
lastValues[path] = formattedVal;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
} else if (path.includes("depth")) {
|
|
874
|
-
formattedVal = rawValue.toFixed(2);
|
|
875
|
-
unit = "m";
|
|
876
|
-
} else {
|
|
877
|
-
if (typeof rawValue === 'number') {
|
|
878
|
-
formattedVal = rawValue.toFixed(6);
|
|
879
|
-
} else {
|
|
880
|
-
formattedVal = String(rawValue);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// 1. Aggiorna la Tabella in alto
|
|
886
|
-
updateTableRow(path, formattedVal, unit, rawValue, source, timeUI);
|
|
887
|
-
|
|
888
|
-
// 2. Aggiungi riga al Log CSV
|
|
889
|
-
if (writeToCsv) {
|
|
890
|
-
addLogEntry(timeISO, path, formattedVal, unit, source, isSpike);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
function updateTableRow(path, val, unit, raw, source, timeUI) {
|
|
895
|
-
const safeId = path.replace(/\./g, '-');
|
|
896
|
-
|
|
897
|
-
let rawStr = "---";
|
|
898
|
-
if (raw !== null && raw !== undefined) {
|
|
899
|
-
if (typeof raw === 'number') {
|
|
900
|
-
rawStr = raw.toFixed(4);
|
|
901
|
-
} else if (typeof raw === 'object') {
|
|
902
|
-
rawStr = JSON.stringify(raw);
|
|
903
|
-
} else {
|
|
904
|
-
rawStr = String(raw);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
const valCell = document.getElementById(`val-${safeId}`);
|
|
909
|
-
if (valCell) {
|
|
910
|
-
valCell.innerHTML = `<span class="value">${val}</span><span class="unit">${unit}</span><span class="raw">Raw: ${rawStr}</span>`;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
const srcCell = document.getElementById(`src-${safeId}`);
|
|
914
|
-
if (srcCell) {
|
|
915
|
-
srcCell.innerHTML = `<span class="source">${source}</span>`;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const timeCell = document.getElementById('time-' + safeId);
|
|
919
|
-
if (timeCell) {
|
|
920
|
-
timeCell.innerHTML = `<span class="time">${timeUI}</span>`;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
const row = document.getElementById(`row-${safeId}`);
|
|
924
|
-
if (row) {
|
|
925
|
-
row.style.backgroundColor = "#334a33";
|
|
926
|
-
setTimeout(() => row.style.backgroundColor = "", 150);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// =========================================================
|
|
931
|
-
// GESTIONE DEL LOG CSV E COPIA APPUNTI
|
|
932
|
-
// =========================================================
|
|
933
|
-
function addLogEntry(timeISO, path, value, unit, source, isSpike) {
|
|
934
|
-
const safeValue = (value.includes(',') || value.includes(';')) ? `"${value}"` : value;
|
|
935
|
-
const safeSource = (source.includes(',') || source.includes(';')) ? `"${source}"` : source;
|
|
936
|
-
const spikeFlag = isSpike ? "TRUE" : "FALSE";
|
|
937
|
-
|
|
938
|
-
const csvLine = `${timeISO},${path},${safeValue},${unit},${safeSource},${spikeFlag}`;
|
|
939
|
-
|
|
940
|
-
logLinesArray.push(csvLine);
|
|
941
|
-
|
|
942
|
-
if (logLinesArray.length > MAX_LOG_LINES) {
|
|
943
|
-
logLinesArray.shift();
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
renderLogTextArea();
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// Filtra ricorsivamente i log visualizzati nel Dump CSV in tempo reale in base alla selezione attiva e visibile
|
|
950
|
-
function renderLogTextArea() {
|
|
951
|
-
const textArea = document.getElementById('log-container');
|
|
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");
|
|
971
|
-
textArea.scrollTop = textArea.scrollHeight;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
function clearLog() {
|
|
975
|
-
logLinesArray = [];
|
|
976
|
-
renderLogTextArea();
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function copyLogCSV() {
|
|
980
|
-
const textArea = document.getElementById('log-container');
|
|
981
|
-
textArea.select();
|
|
982
|
-
textArea.setSelectionRange(0, 999999);
|
|
983
|
-
|
|
984
|
-
try {
|
|
985
|
-
if (navigator.clipboard) {
|
|
986
|
-
navigator.clipboard.writeText(textArea.value).then(() => showCopyNotif());
|
|
987
|
-
} else {
|
|
988
|
-
document.execCommand('copy');
|
|
989
|
-
showCopyNotif();
|
|
990
|
-
}
|
|
991
|
-
} catch (err) {
|
|
992
|
-
console.error("Copia fallita: ", err);
|
|
993
|
-
alert("Error copying to clipboard. Please use CTRL+C / CMD+C manually.");
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
function showCopyNotif() {
|
|
998
|
-
const notif = document.getElementById('copy-notification');
|
|
999
|
-
notif.style.opacity = 1;
|
|
1000
|
-
setTimeout(() => notif.style.opacity = 0, 2000);
|
|
1001
|
-
window.getSelection().removeAllRanges();
|
|
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
|
-
});
|
|
1035
|
-
</script>
|
|
1036
|
-
</body>
|
|
1037
|
-
</html>
|