QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LoggingSettingsDialog.qml
Go to the documentation of this file.
1import QGroundControl
2import QGroundControl.Controls
3import QGroundControl.FactControls
4import QGroundControl.Logging
5import QtQuick
6import QtQuick.Controls
7import QtQuick.Layouts
8
9QGCPopupDialog {
10 QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
11
12 readonly property var appSettings: QGroundControl.settingsManager.appSettings
13 property bool _exportFiltered: false
14 property int _selectedFormatIndex: 0
15 readonly property var exportFormats: [
16 { label: qsTr("Text (.txt)"), filters: [qsTr("Text files (*.txt)"), qsTr("All Files (*)")] },
17 { label: qsTr("JSON (.json)"), filters: [qsTr("JSON files (*.json)"), qsTr("All Files (*)")] },
18 { label: qsTr("CSV (.csv)"), filters: [qsTr("CSV files (*.csv)"), qsTr("All Files (*)")] },
19 { label: qsTr("JSONL (.jsonl)"), filters: [qsTr("JSON Lines files (*.jsonl)"), qsTr("All Files (*)")] }
20 ]
21 readonly property var sink: LogManager.remoteSink
22
23 buttons: Dialog.Close
24 title: qsTr("Logging Settings")
25
26 ColumnLayout {
27 spacing: ScreenTools.defaultFontPixelHeight / 2
28 width: maxContentAvailableWidth
29
30 // ── Disk Logging ─────────────────────────────────────────
31 SettingsGroupLayout {
32 Layout.fillWidth: true
33 heading: qsTr("Disk Logging")
34
35 QGCCheckBoxSlider {
36 Layout.fillWidth: true
37 checked: LogManager.diskLoggingEnabled
38 text: qsTr("Enable disk logging")
39
40 onClicked: LogManager.diskLoggingEnabled = checked
41 }
42
43 QGCLabel {
44 Layout.fillWidth: true
45 color: qgcPal.colorGrey
46 text: qsTr("Log files are saved to: %1").arg(appSettings.logSavePath)
47 visible: LogManager.diskLoggingEnabled
48 wrapMode: Text.WordWrap
49 }
50
51 RowLayout {
52 Layout.fillWidth: true
53 spacing: ScreenTools.defaultFontPixelWidth
54 visible: LogManager.diskLoggingEnabled
55
56 QGCLabel {
57 text: qsTr("Flush on level:")
58 }
59
60 Item {
61 Layout.fillWidth: true
62 }
63
64 QGCComboBox {
65 readonly property var _levelNames: [qsTr("Off"), qsTr("Warning"), qsTr("Critical")]
66 readonly property var _levelValues: [-1, LogEntry.Warning, LogEntry.Critical]
67
68 currentIndex: _levelValues.indexOf(LogManager.flushOnLevel)
69 model: _levelNames
70 sizeToContents: true
71
72 onActivated: index => {
73 LogManager.flushOnLevel = _levelValues[index];
74 }
75 }
76 }
77 }
78
79 // ── Remote Logging ───────────────────────────────────────
80 SettingsGroupLayout {
81 Layout.fillWidth: true
82 heading: qsTr("Remote Logging")
83
84 FactCheckBoxSlider {
85 Layout.fillWidth: true
86 fact: appSettings.remoteLoggingEnabled
87 text: qsTr("Enable remote logging")
88 }
89
90 RowLayout {
91 Layout.fillWidth: true
92 spacing: ScreenTools.defaultFontPixelWidth
93 visible: appSettings.remoteLoggingEnabled.rawValue
94
95 QGCLabel {
96 text: qsTr("Host:")
97 }
98
99 FactTextField {
100 Layout.fillWidth: true
101 fact: appSettings.remoteLoggingHost
102 }
103
104 QGCLabel {
105 text: qsTr("Port:")
106 }
107
108 FactTextField {
109 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 8
110 fact: appSettings.remoteLoggingPort
111 }
112 }
113
114 RowLayout {
115 Layout.fillWidth: true
116 spacing: ScreenTools.defaultFontPixelWidth
117 visible: appSettings.remoteLoggingEnabled.rawValue
118
119 QGCLabel {
120 text: qsTr("Protocol:")
121 }
122
123 Item {
124 Layout.fillWidth: true
125 }
126
127 QGCComboBox {
128 currentIndex: appSettings.remoteLoggingProtocol.rawValue
129 model: [qsTr("UDP"), qsTr("TCP"), qsTr("Auto Fallback")]
130 sizeToContents: true
131
132 onActivated: index => {
133 appSettings.remoteLoggingProtocol.rawValue = index;
134 }
135 }
136 }
137
138 QGCLabel {
139 Layout.fillWidth: true
140 color: qgcPal.colorGrey
141 text: qsTr("UDP is fire-and-forget, TCP is reliable, Auto Fallback starts with UDP and switches to TCP on failures.")
142 visible: appSettings.remoteLoggingEnabled.rawValue
143 wrapMode: Text.WordWrap
144 }
145
146 RowLayout {
147 Layout.fillWidth: true
148 spacing: ScreenTools.defaultFontPixelWidth
149 visible: appSettings.remoteLoggingEnabled.rawValue
150
151 QGCLabel {
152 text: qsTr("Vehicle ID:")
153 }
154
155 FactTextField {
156 Layout.fillWidth: true
157 fact: appSettings.remoteLoggingVehicleId
158 placeholderText: qsTr("Optional identifier")
159 }
160 }
161
162 RowLayout {
163 Layout.fillWidth: true
164 spacing: ScreenTools.defaultFontPixelWidth
165 visible: sink && sink.enabled
166
167 QGCLabel {
168 text: {
169 const kb = sink.bytesSent / 1024;
170 return kb < 1024 ? qsTr("%1 KB sent").arg(Math.round(kb)) : qsTr("%1 MB sent").arg((kb / 1024).toFixed(1));
171 }
172 }
173
174 QGCButton {
175 text: qsTr("Reset")
176
177 onClicked: sink.resetBytesSent()
178 }
179 }
180
181 QGCLabel {
182 Layout.fillWidth: true
183 color: qgcPal.warningText
184 text: sink ? qsTr("Error: %1").arg(sink.lastError) : ""
185 visible: sink && sink.lastError !== ""
186 wrapMode: Text.WordWrap
187 }
188 }
189
190 // ── TLS Encryption ───────────────────────────────────────
191 SettingsGroupLayout {
192 id: tlsGroup
193
194 property string _tlsCaPath: ""
195 property string _tlsCertPath: ""
196 property string _tlsKeyPath: ""
197
198 Layout.fillWidth: true
199 heading: qsTr("TLS Encryption")
200 visible: appSettings.remoteLoggingEnabled.rawValue && appSettings.remoteLoggingProtocol.rawValue !== LogRemoteSink.UDP
201
202 FactCheckBoxSlider {
203 Layout.fillWidth: true
204 fact: appSettings.remoteLoggingTlsEnabled
205 text: qsTr("Enable TLS")
206 }
207
208 FactCheckBoxSlider {
209 Layout.fillWidth: true
210 fact: appSettings.remoteLoggingTlsVerifyPeer
211 text: qsTr("Verify server certificate")
212 visible: appSettings.remoteLoggingTlsEnabled.rawValue
213 }
214
215 RowLayout {
216 Layout.fillWidth: true
217 spacing: ScreenTools.defaultFontPixelWidth
218 visible: appSettings.remoteLoggingTlsEnabled.rawValue
219
220 QGCLabel {
221 text: qsTr("CA Certificate")
222 }
223
224 Item {
225 Layout.fillWidth: true
226 }
227
228 QGCLabel {
229 color: qgcPal.colorGrey
230 text: tlsGroup._tlsCaPath || qsTr("Not loaded")
231 }
232
233 QGCButton {
234 text: qsTr("Browse")
235
236 onClicked: caCertDialog.openForLoad()
237 }
238
239 QGCFileDialog {
240 id: caCertDialog
241
242 nameFilters: [qsTr("PEM files (*.pem)"), qsTr("All Files (*)")]
243 title: qsTr("Select CA Certificate")
244
245 onAcceptedForLoad: file => {
246 if (sink.loadTlsCaCertificates(file))
247 tlsGroup._tlsCaPath = file;
248 }
249 }
250 }
251
252 RowLayout {
253 Layout.fillWidth: true
254 spacing: ScreenTools.defaultFontPixelWidth
255 visible: appSettings.remoteLoggingTlsEnabled.rawValue
256
257 QGCLabel {
258 text: qsTr("Client Certificate")
259 }
260
261 Item {
262 Layout.fillWidth: true
263 }
264
265 QGCButton {
266 text: tlsGroup._tlsCertPath ? qsTr("Cert \u2713") : qsTr("Cert")
267
268 onClicked: clientCertDialog.openForLoad()
269 }
270
271 QGCButton {
272 text: tlsGroup._tlsKeyPath ? qsTr("Key \u2713") : qsTr("Key")
273
274 onClicked: clientKeyDialog.openForLoad()
275 }
276
277 QGCButton {
278 enabled: tlsGroup._tlsCertPath !== "" && tlsGroup._tlsKeyPath !== ""
279 text: qsTr("Load")
280
281 onClicked: sink.loadTlsClientCertificate(tlsGroup._tlsCertPath, tlsGroup._tlsKeyPath)
282 }
283
284 QGCFileDialog {
285 id: clientCertDialog
286
287 nameFilters: [qsTr("PEM files (*.pem)"), qsTr("All Files (*)")]
288 title: qsTr("Select Client Certificate")
289
290 onAcceptedForLoad: file => {
291 tlsGroup._tlsCertPath = file;
292 }
293 }
294
295 QGCFileDialog {
296 id: clientKeyDialog
297
298 nameFilters: [qsTr("PEM files (*.pem)"), qsTr("All Files (*)")]
299 title: qsTr("Select Client Key")
300
301 onAcceptedForLoad: file => {
302 tlsGroup._tlsKeyPath = file;
303 }
304 }
305 }
306
307 QGCLabel {
308 Layout.fillWidth: true
309 color: qgcPal.warningText
310 text: sink ? qsTr("TLS Error: %1").arg(sink.lastTlsError) : ""
311 visible: sink && sink.lastTlsError !== ""
312 wrapMode: Text.WordWrap
313 }
314 }
315
316 // ── Compression ──────────────────────────────────────────
317 SettingsGroupLayout {
318 Layout.fillWidth: true
319 heading: qsTr("Compression")
320 visible: appSettings.remoteLoggingEnabled.rawValue
321
322 FactCheckBoxSlider {
323 Layout.fillWidth: true
324 fact: appSettings.remoteLoggingCompressionEnabled
325 text: qsTr("Enable compression")
326 }
327
328 RowLayout {
329 Layout.fillWidth: true
330 spacing: ScreenTools.defaultFontPixelWidth
331 visible: appSettings.remoteLoggingCompressionEnabled.rawValue
332
333 QGCLabel {
334 text: qsTr("Level")
335 }
336
337 Item {
338 Layout.fillWidth: true
339 }
340
341 QGCLabel {
342 text: Math.round(compressionSlider.value)
343 }
344
345 QGCSlider {
346 id: compressionSlider
347
348 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 15
349 from: 1
350 stepSize: 1
351 to: 9
352 value: appSettings.remoteLoggingCompressionLevel.rawValue
353
354 onMoved: appSettings.remoteLoggingCompressionLevel.rawValue = value
355 }
356 }
357
358 QGCLabel {
359 Layout.fillWidth: true
360 color: qgcPal.colorGrey
361 text: qsTr("Compresses log data using zlib. Level 1 is fastest, level 9 provides best compression.")
362 visible: appSettings.remoteLoggingCompressionEnabled.rawValue
363 wrapMode: Text.WordWrap
364 }
365 }
366
367 // ── Console Buffer ───────────────────────────────────────
368 SettingsGroupLayout {
369 Layout.fillWidth: true
370 heading: qsTr("Console Buffer")
371
372 RowLayout {
373 Layout.fillWidth: true
374 spacing: ScreenTools.defaultFontPixelWidth
375
376 QGCLabel {
377 text: qsTr("Max entries")
378 }
379
380 Item {
381 Layout.fillWidth: true
382 }
383
384 QGCLabel {
385 text: Math.round(bufferSlider.value).toLocaleString()
386 }
387
388 QGCSlider {
389 id: bufferSlider
390
391 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 15
392 from: 10000
393 stepSize: 10000
394 to: 500000
395 value: LogManager.model.maxEntries
396
397 onMoved: LogManager.model.maxEntries = value
398 }
399 }
400
401 QGCLabel {
402 Layout.fillWidth: true
403 color: qgcPal.colorGrey
404 text: qsTr("Number of log entries kept in memory for the console view. Higher values use more RAM.")
405 wrapMode: Text.WordWrap
406 }
407 }
408
409 // ── Log History ──────────────────────────────────────────
410 SettingsGroupLayout {
411 Layout.fillWidth: true
412 heading: qsTr("Log History")
413 visible: LogManager.logStore.isOpen
414
415 QGCLabel {
416 Layout.fillWidth: true
417 color: qgcPal.colorGrey
418 text: qsTr("Session: %1 (%2 entries)").arg(LogManager.logStore.sessionId).arg(LogManager.logStore.entryCount)
419 }
420
421 RowLayout {
422 Layout.fillWidth: true
423 spacing: ScreenTools.defaultFontPixelWidth
424
425 QGCLabel {
426 text: qsTr("Browse session:")
427 }
428
429 QGCComboBox {
430 id: sessionCombo
431
432 Layout.fillWidth: true
433 model: LogManager.historyModel.availableSessions
434 sizeToContents: true
435
436 Component.onCompleted: {
437 if (count > 0)
438 currentIndex = count - 1;
439 }
440 onActivated: LogManager.historyModel.sessionFilter = currentText
441 onModelChanged: {
442 if (count > 0) {
443 const prev = currentText;
444 const idx = find(prev);
445 currentIndex = idx >= 0 ? idx : count - 1;
446 }
447 }
448 }
449 }
450
451 QGCLabel {
452 Layout.fillWidth: true
453 color: qgcPal.colorGrey
454 text: qsTr("%1 entries in selected session").arg(LogManager.historyModel.totalResults)
455 visible: sessionCombo.currentText !== ""
456 }
457
458 RowLayout {
459 Layout.fillWidth: true
460 spacing: ScreenTools.defaultFontPixelWidth
461 visible: sessionCombo.currentText !== ""
462
463 QGCButton {
464 text: qsTr("Export Session")
465
466 onClicked: historyExportDialog.openForSave()
467 }
468
469 QGCButton {
470 text: qsTr("Delete Session")
471
472 onClicked: deleteConfirmDialogFactory.open()
473 }
474 }
475
476 QGCPopupDialogFactory {
477 id: deleteConfirmDialogFactory
478
479 dialogComponent: Component {
480 QGCSimpleMessageDialog {
481 title: qsTr("Confirm Delete")
482 text: qsTr("Delete session \"%1\" and all its log entries?").arg(sessionCombo.currentText)
483 buttons: Dialog.Yes | Dialog.No
484
485 onAccepted: LogManager.logStore.deleteSession(sessionCombo.currentText)
486 }
487 }
488 }
489
490 QGCFileDialog {
491 id: historyExportDialog
492
493 folder: QGroundControl.settingsManager.appSettings.logSavePath
494 nameFilters: exportFormats[_selectedFormatIndex].filters
495 title: qsTr("Export session log")
496
497 onAcceptedForSave: file => {
498 LogManager.logStore.exportSession(sessionCombo.currentText, file, _selectedFormatIndex);
499 }
500 }
501 }
502
503 // ── GStreamer ─────────────────────────────────────────────
504 SettingsGroupLayout {
505 Layout.fillWidth: true
506 heading: qsTr("GStreamer")
507 visible: appSettings.gstDebugLevel.userVisible
508
509 RowLayout {
510 Layout.fillWidth: true
511 spacing: ScreenTools.defaultFontPixelWidth
512
513 QGCLabel {
514 text: qsTr("Debug level:")
515 }
516
517 Item {
518 Layout.fillWidth: true
519 }
520
521 FactComboBox {
522 fact: appSettings.gstDebugLevel
523 sizeToContents: true
524 }
525 }
526 }
527
528 // ── Export ────────────────────────────────────────────────
529 SettingsGroupLayout {
530 Layout.fillWidth: true
531 heading: qsTr("Export")
532
533 RowLayout {
534 Layout.fillWidth: true
535 spacing: ScreenTools.defaultFontPixelWidth
536
537 QGCLabel {
538 text: qsTr("Format:")
539 }
540
541 Item {
542 Layout.fillWidth: true
543 }
544
545 QGCComboBox {
546 id: exportFormatCombo
547
548 currentIndex: _selectedFormatIndex
549 model: exportFormats.map(f => f.label)
550
551 onActivated: _selectedFormatIndex = currentIndex
552 }
553 }
554
555 RowLayout {
556 Layout.fillWidth: true
557 spacing: ScreenTools.defaultFontPixelWidth
558
559 QGCButton {
560 text: qsTr("Save Log")
561
562 onClicked: {
563 _exportFiltered = false;
564 saveFileDialog.openForSave();
565 }
566 }
567
568 QGCButton {
569 text: qsTr("Save Filtered")
570 visible: LogManager.model.filterLevel > LogEntry.Debug || LogManager.model.filterCategory !== "" || LogManager.model.filterText !== ""
571
572 onClicked: {
573 _exportFiltered = true;
574 saveFileDialog.openForSave();
575 }
576 }
577 }
578 }
579
580 // ── Actions ──────────────────────────────────────────────
581 SettingsGroupLayout {
582 Layout.fillWidth: true
583 heading: qsTr("Actions")
584
585 RowLayout {
586 Layout.fillWidth: true
587 spacing: ScreenTools.defaultFontPixelWidth
588
589 QGCButton {
590 text: qsTr("Clear Log")
591
592 onClicked: LogManager.model.clear()
593 }
594
595 QGCButton {
596 enabled: LogManager.diskLoggingEnabled
597 text: qsTr("Flush to Disk")
598
599 onClicked: LogManager.flush()
600 }
601 }
602 }
603 }
604
605 QGCFileDialog {
606 id: saveFileDialog
607
608 folder: QGroundControl.settingsManager.appSettings.logSavePath
609 nameFilters: exportFormats[_selectedFormatIndex].filters
610 title: _exportFiltered ? qsTr("Save filtered log") : qsTr("Save app log")
611
612 onAcceptedForSave: file => {
613 if (_exportFiltered)
614 LogManager.writeFilteredMessages(file, _selectedFormatIndex);
615 else
616 LogManager.writeMessages(file, _selectedFormatIndex);
617 }
618 }
619}