diff --git a/data/debug-interface/debug-interface.qrc b/data/debug-interface/debug-interface.qrc
index 822ac02c..89b7a29d 100644
--- a/data/debug-interface/debug-interface.qrc
+++ b/data/debug-interface/debug-interface.qrc
@@ -1,8 +1,15 @@
-
+
logo.svg
warning.svg
+ script.js
styles.css
+ favicons/favicon-16x16.png
+ favicons/favicon-32x32.png
+ favicons/favicon-96x96.png
+ favicons/favicon-128.png
+ favicons/favicon-196x196.png
+ favicons/favicon.ico
+ favicons/favicon.svg
-
diff --git a/data/debug-interface/favicons/apple-touch-icon-114x114.png b/data/debug-interface/favicons/apple-touch-icon-114x114.png
new file mode 100644
index 00000000..f67b4a2d
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-114x114.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-120x120.png b/data/debug-interface/favicons/apple-touch-icon-120x120.png
new file mode 100644
index 00000000..bc715687
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-120x120.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-144x144.png b/data/debug-interface/favicons/apple-touch-icon-144x144.png
new file mode 100644
index 00000000..f4ebd8df
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-144x144.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-152x152.png b/data/debug-interface/favicons/apple-touch-icon-152x152.png
new file mode 100644
index 00000000..ec83a6a8
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-152x152.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-57x57.png b/data/debug-interface/favicons/apple-touch-icon-57x57.png
new file mode 100644
index 00000000..f5c78f9d
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-57x57.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-60x60.png b/data/debug-interface/favicons/apple-touch-icon-60x60.png
new file mode 100644
index 00000000..51ff9932
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-60x60.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-72x72.png b/data/debug-interface/favicons/apple-touch-icon-72x72.png
new file mode 100644
index 00000000..558d2d06
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-72x72.png differ
diff --git a/data/debug-interface/favicons/apple-touch-icon-76x76.png b/data/debug-interface/favicons/apple-touch-icon-76x76.png
new file mode 100644
index 00000000..4c15f845
Binary files /dev/null and b/data/debug-interface/favicons/apple-touch-icon-76x76.png differ
diff --git a/data/debug-interface/favicons/favicon-128.png b/data/debug-interface/favicons/favicon-128.png
new file mode 100644
index 00000000..fe3ff757
Binary files /dev/null and b/data/debug-interface/favicons/favicon-128.png differ
diff --git a/data/debug-interface/favicons/favicon-16x16.png b/data/debug-interface/favicons/favicon-16x16.png
new file mode 100644
index 00000000..6581a72f
Binary files /dev/null and b/data/debug-interface/favicons/favicon-16x16.png differ
diff --git a/data/debug-interface/favicons/favicon-196x196.png b/data/debug-interface/favicons/favicon-196x196.png
new file mode 100644
index 00000000..d0a4bd55
Binary files /dev/null and b/data/debug-interface/favicons/favicon-196x196.png differ
diff --git a/data/debug-interface/favicons/favicon-32x32.png b/data/debug-interface/favicons/favicon-32x32.png
new file mode 100644
index 00000000..64bb80f9
Binary files /dev/null and b/data/debug-interface/favicons/favicon-32x32.png differ
diff --git a/data/debug-interface/favicons/favicon-96x96.png b/data/debug-interface/favicons/favicon-96x96.png
new file mode 100644
index 00000000..9539882f
Binary files /dev/null and b/data/debug-interface/favicons/favicon-96x96.png differ
diff --git a/data/debug-interface/favicons/favicon.ico b/data/debug-interface/favicons/favicon.ico
new file mode 100644
index 00000000..a39e20a8
Binary files /dev/null and b/data/debug-interface/favicons/favicon.ico differ
diff --git a/data/debug-interface/favicons/favicon.svg b/data/debug-interface/favicons/favicon.svg
new file mode 100644
index 00000000..a59d0af1
--- /dev/null
+++ b/data/debug-interface/favicons/favicon.svg
@@ -0,0 +1,57 @@
+
+
+
+
\ No newline at end of file
diff --git a/data/debug-interface/favicons/mstile-144x144.png b/data/debug-interface/favicons/mstile-144x144.png
new file mode 100644
index 00000000..f4ebd8df
Binary files /dev/null and b/data/debug-interface/favicons/mstile-144x144.png differ
diff --git a/data/debug-interface/favicons/mstile-150x150.png b/data/debug-interface/favicons/mstile-150x150.png
new file mode 100644
index 00000000..9620ec3b
Binary files /dev/null and b/data/debug-interface/favicons/mstile-150x150.png differ
diff --git a/data/debug-interface/favicons/mstile-310x150.png b/data/debug-interface/favicons/mstile-310x150.png
new file mode 100644
index 00000000..a3356003
Binary files /dev/null and b/data/debug-interface/favicons/mstile-310x150.png differ
diff --git a/data/debug-interface/favicons/mstile-310x310.png b/data/debug-interface/favicons/mstile-310x310.png
new file mode 100644
index 00000000..28ac2872
Binary files /dev/null and b/data/debug-interface/favicons/mstile-310x310.png differ
diff --git a/data/debug-interface/favicons/mstile-70x70.png b/data/debug-interface/favicons/mstile-70x70.png
new file mode 100644
index 00000000..fe3ff757
Binary files /dev/null and b/data/debug-interface/favicons/mstile-70x70.png differ
diff --git a/data/debug-interface/script.js b/data/debug-interface/script.js
new file mode 100644
index 00000000..def30e22
--- /dev/null
+++ b/data/debug-interface/script.js
@@ -0,0 +1,152 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2018 Simon Stürz *
+ * *
+ * This file is part of nymea. *
+ * *
+ * nymea is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, version 2 of the License. *
+ * *
+ * nymea is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with nymea. If not, see . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+/* ========================================================================*/
+/* Websocket connection
+/* ========================================================================*/
+
+var webSocket = null;
+
+function connectWebsocket() {
+ var urlString = "ws://" + window.location.hostname + ":2626"
+ console.log("Connecting to: " + urlString);
+
+ try {
+ webSocket = new WebSocket(urlString);
+
+ webSocket.onopen = function(openEvent) {
+ console.log("WebSocket connected: " + JSON.stringify(openEvent, null, 4));
+ document.getElementById("connectWebsocketButton").disabled = true;
+ document.getElementById("disconnectWebsocketButton").disabled = false;
+ };
+
+ webSocket.onclose = function (closeEvent) {
+ console.log("WebSocket disconnected: " + JSON.stringify(closeEvent, null, 4));
+ document.getElementById("connectWebsocketButton").disabled = false;
+ document.getElementById("disconnectWebsocketButton").disabled = true;
+ };
+
+ webSocket.onerror = function (errorEvent) {
+ console.log("WebSocket error: " + JSON.stringify(errorEvent, null, 4));
+ };
+
+ webSocket.onmessage = function (messageEvent) {
+ var message = messageEvent.data;
+ console.log("WebSocket data received: " + message);
+ document.getElementById("logsTextArea").value += message;
+ };
+
+ } catch (exception) {
+ console.error(exception);
+ }
+
+}
+
+function disconnectWebsocket() {
+ console.log("Disconnecting from: " + webSocket.url);
+ webSocket.close()
+}
+
+
+
+/* ========================================================================*/
+/* File download function
+/* ========================================================================*/
+
+function downloadFile(filePath, fileName) {
+ console.log("Download file requested " + filePath + " --> " + fileName);
+ var element = document.createElement('a');
+ element.setAttribute('href', filePath);
+ element.setAttribute('download', fileName);
+ element.style.display = 'none';
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
+}
+
+/* ========================================================================*/
+/* Network test functions
+/* ========================================================================*/
+
+function startPingTest() {
+ console.log("Start ping test");
+ var textArea = document.getElementById("pingTextArea");
+ var button = document.getElementById("pingButton");
+ // Clear the text output
+ textArea.value = "";
+
+ // Request ping output
+ var request = new XMLHttpRequest();
+ request.open("GET", "/debug/ping", true);
+ request.send(null);
+ button.disabled = true
+ request.onreadystatechange = function() {
+ if (request.readyState == 4) {
+ console.log(request.responseText);
+ textArea.value = request.responseText
+ button.disabled = false
+ }
+ };
+}
+
+function startDigTest() {
+ console.log("Start dig test");
+ var textArea = document.getElementById("digTextArea");
+ var button = document.getElementById("digButton");
+
+ // Clear the text output
+ textArea.value = "";
+
+ // Request dig output
+ var request = new XMLHttpRequest();
+ request.open("GET", "/debug/dig", true);
+ request.send(null);
+ button.disabled = true
+ request.onreadystatechange = function() {
+ if (request.readyState == 4) {
+ console.log(request.responseText);
+ textArea.value = request.responseText
+ button.disabled = false
+ }
+ };
+}
+
+function startTracePathTest() {
+ console.log("Start trace path test");
+ var textArea = document.getElementById("tracePathTextArea");
+ var button = document.getElementById("tracePathButton");
+
+ // Clear the text output
+ textArea.value = "";
+
+ // Request dig output
+ var request = new XMLHttpRequest();
+ request.open("GET", "/debug/tracepath", true);
+ request.send(null);
+ button.disabled = true
+ request.onreadystatechange = function() {
+ if (request.readyState == 4) {
+ console.log(request.responseText);
+ textArea.value = request.responseText
+ button.disabled = false
+ }
+ };
+}
diff --git a/data/debug-interface/styles.css b/data/debug-interface/styles.css
index 8baf78c6..0397a77d 100644
--- a/data/debug-interface/styles.css
+++ b/data/debug-interface/styles.css
@@ -20,120 +20,144 @@
html,
body {
- margin:0;
- padding:0;
- height:100%;
+ margin:0;
+ padding:0;
+ height:100%;
}
p {
- color: #676767;
- font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
- font-size: 100%;
- text-align: center;
+ color: #676767;
+ font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
+ font-size: 100%;
+ text-align: center;
}
h1, h2, h3, h4, h5, h6 {
- font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
- font-weight: normal;
- color: #676767;
- text-transform: none;
+ font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
+ font-weight: normal;
+ color: #676767;
+ text-transform: none;
}
th, td {
- padding: 3px;
- padding-left: 10px;
- padding-right: 10px;
- color: #676767;
- font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
- text-align: left;
+ padding: 3px;
+ padding-left: 10px;
+ padding-right: 10px;
+ color: #676767;
+ font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
+ text-align: left;
}
hr {
- color: #efefef;
+ color: #efefef;
}
table {
- display: table;
- border-collapse: colapse;
- border-color: #efefef;
- width: 100%;
+ display: table;
+ border-collapse: colapse;
+ border-color: #efefef;
+ min-height: 100px;
+ width: 100%;
}
button {
- width: 100%;
+ width: 100%;
+}
+
+textarea {
+ background-color: #3a4055;
+ width: 100%;
+ padding: 15px;
+ min-height: 100px;
+ text-align: left;
+ border-radius: 10px;
+}
+
+.console-textarea {
+ color: white;
+ margin-top: 20px;
+ padding: 15px;
+ font-family: "Ubuntu Mono";
+ font-size: 100%;
}
.nymea-main-logo {
- left: 0;
- height: 85px;
- min-height: 85px;
- max-height: 85px;
- vertical-align: middle;
+ left: 0;
+ height: 85px;
+ min-height: 85px;
+ max-height: 85px;
+ vertical-align: middle;
}
.warning {
- background-color: #ed3146;
- border-radius: 20px;
- opacity: 0.8;
- width: 80%;
- margin-left: auto;
- margin-right: auto;
+ background-color: #ed3146;
+ border-radius: 10px;
+ opacity: 0.8;
+ width: 80%;
+ margin-left: auto;
+ margin-right: auto;
}
.warning-message {
- padding:30px;
- margin-left: 60px;
- opacity: 1;
- color: white;
- background-color: transparent;
- font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
- text-align: center;
- float: none;
+ padding: 30px;
+ margin-left: 60px;
+ opacity: 1;
+ color: white;
+ background-color: transparent;
+ font-family: "Ubuntu", Helvetica, "Helvetica Neue", Arial, sans-serif;
+ text-align: center;
+ float: none;
}
.warning-image {
- padding:12px;
- height: 60px;
- min-height: 60px;
- max-height: 60px;
- float: left;
+ padding:12px;
+ height: 60px;
+ min-height: 60px;
+ max-height: 60px;
+ float: left;
}
.warning:after {
- content: "";
- display: table;
- clear: both;
+ content: "";
+ display: table;
+ clear: both;
}
.download-row {
- width: 100%;
- height: 100px;
- left: 0;
- right: 0;
+ width: 100%;
+ height: 100px;
+ left: 0;
+ right: 0;
}
.download-name-column {
- float: left;
- width: 30%;
- padding: 10px;
+ float: left;
+ width: 20%;
+ padding: 10px;
}
.download-path-column {
- float: left;
- width: 40%;
- padding: 10px;
+ float: left;
+ width: 40%;
+ padding: 10px;
}
.download-button-column {
- float: left;
- width: 20%;
- padding: 10px;
+ float: left;
+ width: 20%;
+ padding: 10px;
+}
+
+.show-button-column {
+ float: left;
+ width: 10%;
+ padding: 10px;
}
.download-row: after {
- content: "";
- display: table;
- clear: both;
+ content: "";
+ display: table;
+ clear: both;
}
.button {
@@ -146,34 +170,45 @@ button {
display: inline-block;
font-size: 16px;
border-radius: 10px;
+ opacity: 0.8;
+ transition: 0.3s;
}
+.button:hover {
+ opacity: 1
+}
+
+.button:disabled,
+.button[disabled]{
+ background-color: #cccccc;
+ color: #676767;
+}
.container {
- min-height:100%;
- position:relative;
+ min-height:100%;
+ position:relative;
}
.header {
- padding:10px;
- background-color: #efefef;
- text-align: center;
+ padding:10px;
+ background-color: #efefef;
+ text-align: center;
}
.body {
- padding-left: 15%;
- padding-right: 15%;
- padding-bottom:120px;
+ padding-left: 15%;
+ padding-right: 15%;
+ padding-bottom:120px;
}
.footer {
- position:absolute;
- bottom:0;
- left:0;
- right:0;
- width:100%;
- height:100px;
- background-color: #efefef;
- color: #676767;
- text-align: center;
+ position:absolute;
+ bottom:0;
+ left:0;
+ right:0;
+ width:100%;
+ height:100px;
+ background-color: #efefef;
+ color: #676767;
+ text-align: center;
}
diff --git a/libnymea-core/debugserverhandler.cpp b/libnymea-core/debugserverhandler.cpp
index 41899c06..92cf1f12 100644
--- a/libnymea-core/debugserverhandler.cpp
+++ b/libnymea-core/debugserverhandler.cpp
@@ -24,15 +24,363 @@
#include "httprequest.h"
#include "loggingcategories.h"
#include "debugserverhandler.h"
+#include "stdio.h"
#include
#include
+#include
+
namespace nymeaserver {
-DebugServerHandler::DebugServerHandler(QObject *parent) : QObject(parent)
+DebugServerHandler::DebugServerHandler(QObject *parent) :
+ QObject(parent)
{
+ m_websocketServer = new QWebSocketServer("Debug server", QWebSocketServer::NonSecureMode, this);
+ connect(m_websocketServer, &QWebSocketServer::newConnection, this, &DebugServerHandler::onWebsocketClientConnected);
+ // FIXME: enable disable server with debug server
+ if (!m_websocketServer->listen(QHostAddress::Any, 2626)) {
+ qCWarning(dcWebServer()) << "DebugServer: The debug server websocket interface could not listen on" << m_websocketServer->serverUrl().toString();
+ }
+ qCDebug(dcWebServer()) << "DebugServer: Started debug server websocket interface on" << m_websocketServer->serverUrl().toString();
+
+ m_timer = new QTimer(this);
+ m_timer->setSingleShot(false);
+ m_timer->setInterval(1000);
+ connect(m_timer, &QTimer::timeout, this, &DebugServerHandler::onTimeout);
+
+ //m_timer->start();
+
+ qInstallMessageHandler(&DebugServerHandler::consoleLogHandler);
+}
+
+HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath)
+{
+ qCDebug(dcWebServer()) << "DebugServer: Debug request for" << requestPath;
+
+ // Check if debug page request
+ if (requestPath == "/debug" || requestPath == "/debug/") {
+ qCDebug(dcWebServer()) << "DebugServer: Create debug interface page";
+ // Fallback default debug page
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createDebugXmlDocument());
+ return reply;
+ }
+
+ // Check if this is a logdb requested
+ if (requestPath.startsWith("/debug/logdb.sql")) {
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << NymeaCore::instance()->configuration()->logDBName();
+ QFile logDatabaseFile(NymeaCore::instance()->configuration()->logDBName());
+ if (!logDatabaseFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName() << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ //: The HTTP error message of the debug interface. The %1 represents the file name.
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(logDatabaseFile.fileName())));
+ return reply;
+ }
+
+ if (!logDatabaseFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName();
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ //: The HTTP error message of the debug interface. The %1 represents the file name.
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(logDatabaseFile.fileName())));
+ return reply;
+ }
+
+ QByteArray logDatabaseRawData = logDatabaseFile.readAll();
+ logDatabaseFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "application/sql");
+ reply->setPayload(logDatabaseRawData);
+ return reply;
+ }
+
+
+ // Check if this is a syslog requested
+ if (requestPath.startsWith("/debug/syslog")) {
+ QString syslogFileName = "/var/log/syslog";
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << syslogFileName;
+ QFile syslogFile(syslogFileName);
+ if (!syslogFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read log database file for debug download" << syslogFileName << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(syslogFileName)));
+ return reply;
+ }
+
+ if (!syslogFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read syslog file for debug download" << syslogFileName;
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(syslogFileName)));
+ return reply;
+ }
+
+ QByteArray syslogFileData = syslogFile.readAll();
+ syslogFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
+ reply->setPayload(syslogFileData);
+ return reply;
+ }
+
+ // Check if this is a settings request
+ if (requestPath.startsWith("/debug/settings")) {
+ if (requestPath.startsWith("/debug/settings/devices")) {
+ QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleDevices).fileName();
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << settingsFileName;
+ QFile settingsFile(settingsFileName);
+ if (!settingsFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ if (!settingsFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName;
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ QByteArray settingsFileData = settingsFile.readAll();
+ settingsFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
+ reply->setPayload(settingsFileData);
+ return reply;
+ }
+
+ if (requestPath.startsWith("/debug/settings/rules")) {
+ QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleRules).fileName();
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << settingsFileName;
+ QFile settingsFile(settingsFileName);
+ if (!settingsFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ if (!settingsFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName;
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ QByteArray settingsFileData = settingsFile.readAll();
+ settingsFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
+ reply->setPayload(settingsFileData);
+ return reply;
+ }
+
+ if (requestPath.startsWith("/debug/settings/nymead")) {
+ QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName();
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << settingsFileName;
+ QFile settingsFile(settingsFileName);
+ if (!settingsFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ if (!settingsFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName;
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ QByteArray settingsFileData = settingsFile.readAll();
+ settingsFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
+ reply->setPayload(settingsFileData);
+ return reply;
+ }
+
+ if (requestPath.startsWith("/debug/settings/devicestates")) {
+ QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).fileName();
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << settingsFileName;
+ QFile settingsFile(settingsFileName);
+ if (!settingsFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ if (!settingsFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName;
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ QByteArray settingsFileData = settingsFile.readAll();
+ settingsFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
+ reply->setPayload(settingsFileData);
+ return reply;
+ }
+
+ if (requestPath.startsWith("/debug/settings/plugins")) {
+ QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRolePlugins).fileName();
+ qCDebug(dcWebServer()) << "DebugServer: Loading" << settingsFileName;
+ QFile settingsFile(settingsFileName);
+ if (!settingsFile.exists()) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName << "file does not exist.";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ if (!settingsFile.open(QFile::ReadOnly)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not read file for debug download" << settingsFileName;
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
+ reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
+ return reply;
+ }
+
+ QByteArray settingsFileData = settingsFile.readAll();
+ settingsFile.close();
+
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
+ reply->setPayload(settingsFileData);
+ return reply;
+ }
+ }
+
+ if (requestPath.startsWith("/debug/ping")) {
+ // Only one ping process should run
+ if (m_pingProcess || m_pingReply)
+ return RestResource::createErrorReply(HttpReply::InternalServerError);
+
+ qCDebug(dcWebServer()) << "DebugServer: Start ping nymea.io process";
+ m_pingProcess = new QProcess(this);
+ m_pingProcess->setProcessChannelMode(QProcess::MergedChannels);
+ connect(m_pingProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onPingProcessFinished(int,QProcess::ExitStatus)));
+ m_pingProcess->start("ping", { "-c", "4", "nymea.io" } );
+
+ m_pingReply = RestResource::createAsyncReply();
+ return m_pingReply;
+ }
+
+ if (requestPath.startsWith("/debug/dig")) {
+ // Only one dig process should run
+ if (m_digProcess || m_digReply)
+ return RestResource::createErrorReply(HttpReply::InternalServerError);
+
+ qCDebug(dcWebServer()) << "DebugServer: Start dig nymea.io process";
+ m_digProcess = new QProcess(this);
+ m_digProcess->setProcessChannelMode(QProcess::MergedChannels);
+ connect(m_digProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onDigProcessFinished(int,QProcess::ExitStatus)));
+ m_digProcess->start("dig", { "nymea.io" } );
+
+ m_digReply = RestResource::createAsyncReply();
+ return m_digReply;
+ }
+
+ if (requestPath.startsWith("/debug/tracepath")) {
+ // Only one tracepath process should run
+ if (m_tracePathProcess || m_tracePathReply)
+ return RestResource::createErrorReply(HttpReply::InternalServerError);
+
+ qCDebug(dcWebServer()) << "DebugServer: Start tracepath nymea.io process";
+ m_tracePathProcess = new QProcess(this);
+ m_tracePathProcess->setProcessChannelMode(QProcess::MergedChannels);
+ connect(m_tracePathProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onTracePathProcessFinished(int,QProcess::ExitStatus)));
+ m_tracePathProcess->start("tracepath", { "nymea.io" } );
+
+ m_tracePathReply = RestResource::createAsyncReply();
+ return m_tracePathReply;
+ }
+
+
+ // Check if this is a resource file request
+ if (resourceFileExits(requestPath)) {
+ return processDebugFileRequest(requestPath);
+ }
+
+ // If nothing matches, redirect to /debug page
+ qCWarning(dcWebServer()) << "DebugServer: Resource for debug interface not found. Redirecting to /debug";
+ HttpReply *reply = RestResource::createErrorReply(HttpReply::PermanentRedirect);
+ reply->setHeader(HttpReply::LocationHeader, "/debug");
+ return reply;
+}
+
+QByteArray DebugServerHandler::loadResourceData(const QString &resourceFileName)
+{
+ QFile resourceFile(QString(":%1").arg(resourceFileName));
+ if (!resourceFile.open(QFile::ReadOnly | QFile::Text)) {
+ qCWarning(dcWebServer()) << "DebugServer: Could not open resource file" << resourceFile.fileName();
+ return QByteArray();
+ }
+
+ return resourceFile.readAll();
+}
+
+QString DebugServerHandler::getResourceFileName(const QString &requestPath)
+{
+ return QString(requestPath).remove("/debug");
+}
+
+bool DebugServerHandler::resourceFileExits(const QString &requestPath)
+{
+ QFile resourceFile(QString(":%1").arg(getResourceFileName(requestPath)));
+ return resourceFile.exists();
+}
+
+HttpReply *DebugServerHandler::processDebugFileRequest(const QString &requestPath)
+{
+ // Here we already know that the resource file exists
+ QString resourceFileName = getResourceFileName(requestPath);
+ QByteArray data = loadResourceData(resourceFileName);
+
+ // Create reply for resource file
+ HttpReply *reply = RestResource::createSuccessReply();
+ reply->setPayload(data);
+
+ // Check content type
+ if (resourceFileName.endsWith(".css")) {
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/css; charset=\"utf-8\";");
+ } else if (resourceFileName.endsWith(".svg")) {
+ reply->setHeader(HttpReply::ContentTypeHeader, "image/svg+xml; charset=\"utf-8\";");
+ } else if (resourceFileName.endsWith(".js")) {
+ reply->setHeader(HttpReply::ContentTypeHeader, "text/javascript; charset=\"utf-8\";");
+ } else if (resourceFileName.endsWith(".png")) {
+ reply->setHeader(HttpReply::ContentTypeHeader, "image/png");
+ }
+
+ return reply;
}
QByteArray DebugServerHandler::createDebugXmlDocument()
@@ -43,6 +391,7 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeStartDocument("1.0");
writer.writeProcessingInstruction("DOCUMENT", "html");
writer.writeComment("Auto generated html page from nymea server");
+
writer.writeStartElement("html");
writer.writeAttribute("lang", NymeaCore::instance()->configuration()->locale().name());
@@ -57,6 +406,44 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("rel", "stylesheet");
writer.writeAttribute("href", "/debug/styles.css");
+ writer.writeStartElement("script");
+ writer.writeAttribute("type", "application/javascript");
+ writer.writeAttribute("src", "/debug/script.js");
+ writer.writeCharacters("");
+ writer.writeEndElement(); // script
+
+ // Favicons
+ writer.writeEmptyElement("link");
+ writer.writeAttribute("rel", "icon");
+ writer.writeAttribute("type", "image/png");
+ writer.writeAttribute("sizes", "16x16");
+ writer.writeAttribute("href", "/debug/favicons/favicon-16x16.png");
+
+ writer.writeEmptyElement("link");
+ writer.writeAttribute("rel", "icon");
+ writer.writeAttribute("type", "image/png");
+ writer.writeAttribute("sizes", "32x32");
+ writer.writeAttribute("href", "/debug/favicons/favicon-32x32.png");
+
+ writer.writeEmptyElement("link");
+ writer.writeAttribute("rel", "icon");
+ writer.writeAttribute("type", "image/png");
+ writer.writeAttribute("sizes", "64x64");
+ writer.writeAttribute("href", "/debug/favicons/favicon-64x64.png");
+
+ writer.writeEmptyElement("link");
+ writer.writeAttribute("rel", "icon");
+ writer.writeAttribute("type", "image/png");
+ writer.writeAttribute("sizes", "128x128");
+ writer.writeAttribute("href", "/debug/favicons/favicon-128.png");
+
+ writer.writeEmptyElement("link");
+ writer.writeAttribute("rel", "icon");
+ writer.writeAttribute("type", "image/png");
+ writer.writeAttribute("sizes", "196x196");
+ writer.writeAttribute("href", "/debug/favicons/favicon-196x196.png");
+
+
//: The header title of the debug server interface
writer.writeTextElement("title", tr("Debug nymea"));
@@ -276,11 +663,13 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/logdb.sql");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaCore::instance()->configuration()->logDBName())) {
+ writer.writeAttribute("disabled", "disabled");
+ }
+ writer.writeAttribute("onClick", "downloadFile('/debug/logdb.sql', 'logdb.sql')");
//: The download button description of the debug interface
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
@@ -289,7 +678,6 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeEndElement(); // div download-row
-
// Download row
writer.writeStartElement("div");
writer.writeAttribute("class", "download-row");
@@ -309,16 +697,28 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/syslog");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("onClick", "downloadFile('/debug/syslog', 'syslog.log')");
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
writer.writeEndElement(); // form
writer.writeEndElement(); // div download-button-column
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "show-button-column");
+ writer.writeStartElement("form");
+ writer.writeAttribute("class", "show-button");
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("onClick", "window.open('/debug/syslog', '_blank')");
+ writer.writeCharacters(tr("Show"));
+ writer.writeEndElement(); // button
+ writer.writeEndElement(); // form
+ writer.writeEndElement(); // div show-button-column
+
writer.writeEndElement(); // div download-row
@@ -347,16 +747,34 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/settings/nymead");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName())) {
+ writer.writeAttribute("disabled", "disabled");
+ }
+ writer.writeAttribute("onClick", "downloadFile('/debug/settings/nymead', 'nymead.conf')");
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
writer.writeEndElement(); // form
writer.writeEndElement(); // div download-button-column
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "show-button-column");
+ writer.writeStartElement("form");
+ writer.writeAttribute("class", "show-button");
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName())) {
+ writer.writeAttribute("disabled", "disabled");
+ }
+ writer.writeAttribute("onClick", "window.open('/debug/settings/nymead', '_blank')");
+ writer.writeCharacters(tr("Show"));
+ writer.writeEndElement(); // button
+ writer.writeEndElement(); // form
+ writer.writeEndElement(); // div show-button-column
+
writer.writeEndElement(); // div download-row
@@ -379,16 +797,34 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/settings/devices");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleDevices).fileName())) {
+ writer.writeAttribute("disabled", "disabled");
+ }
+ writer.writeAttribute("onClick", "downloadFile('/debug/settings/devices', 'devices.conf')");
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
writer.writeEndElement(); // form
writer.writeEndElement(); // div download-button-column
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "show-button-column");
+ writer.writeStartElement("form");
+ writer.writeAttribute("class", "show-button");
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleDevices).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "window.open('/debug/settings/devices', '_blank')");
+ writer.writeCharacters(tr("Show"));
+ writer.writeEndElement(); // button
+ writer.writeEndElement(); // form
+ writer.writeEndElement(); // div show-button-column
+
writer.writeEndElement(); // div download-row
@@ -411,16 +847,34 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/settings/devicestates");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "downloadFile('/debug/settings/devicestates', 'devicestates.conf')");
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
writer.writeEndElement(); // form
writer.writeEndElement(); // div download-button-column
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "show-button-column");
+ writer.writeStartElement("form");
+ writer.writeAttribute("class", "show-button");
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "window.open('/debug/settings/devicestates', '_blank')");
+ writer.writeCharacters(tr("Show"));
+ writer.writeEndElement(); // button
+ writer.writeEndElement(); // form
+ writer.writeEndElement(); // div show-button-column
+
writer.writeEndElement(); // div download-row
@@ -443,16 +897,34 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/settings/rules");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleRules).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "downloadFile('/debug/settings/rules', 'rules.conf')");
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
writer.writeEndElement(); // form
writer.writeEndElement(); // div download-button-column
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "show-button-column");
+ writer.writeStartElement("form");
+ writer.writeAttribute("class", "show-button");
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleRules).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "window.open('/debug/settings/rules', '_blank')");
+ writer.writeCharacters(tr("Show"));
+ writer.writeEndElement(); // button
+ writer.writeEndElement(); // form
+ writer.writeEndElement(); // div show-button-column
+
writer.writeEndElement(); // div download-row
@@ -475,20 +947,168 @@ QByteArray DebugServerHandler::createDebugXmlDocument()
writer.writeAttribute("class", "download-button-column");
writer.writeStartElement("form");
writer.writeAttribute("class", "download-button");
- writer.writeAttribute("method", "get");
- writer.writeAttribute("action", "/debug/settings/plugins");
writer.writeStartElement("button");
writer.writeAttribute("class", "button");
- writer.writeAttribute("type", "submit");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRolePlugins).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "downloadFile('/debug/settings/plugins', 'plugins.conf')");
writer.writeCharacters(tr("Download"));
writer.writeEndElement(); // button
writer.writeEndElement(); // form
writer.writeEndElement(); // div download-button-column
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "show-button-column");
+ writer.writeStartElement("form");
+ writer.writeAttribute("class", "show-button");
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRolePlugins).fileName())) {
+ writer.writeAttribute("disabled", "true");
+ }
+ writer.writeAttribute("onClick", "window.open('/debug/settings/plugins', '_blank')");
+ writer.writeCharacters(tr("Show"));
+ writer.writeEndElement(); // button
+ writer.writeEndElement(); // form
+ writer.writeEndElement(); // div show-button-column
+
writer.writeEndElement(); // div download-row
- writer.writeEndElement(); // div body
+ // Network section
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "network");
+ writer.writeEmptyElement("hr");
+ //: The network section of the debug interface
+ writer.writeTextElement("h2", tr("Network"));
+
+ //: The network section description of the debug interface
+ writer.writeTextElement("p", tr("This section allows you to perform different network connectivity tests in order "
+ "to find out if the device nymea is running on is online and has full network connectivity."));
+
+
+ // Ping section
+ writer.writeEmptyElement("hr");
+ //: The ping section of the debug interface
+ writer.writeTextElement("h3", tr("Ping nymea.io"));
+ writer.writeEmptyElement("hr");
+
+ // Start ping button
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("id", "pingButton");
+ writer.writeAttribute("onClick", "startPingTest()");
+ //: The ping button text of the debug interface
+ writer.writeCharacters(tr("Start ping test"));
+ writer.writeEndElement(); // button
+
+ // Ping output
+ writer.writeStartElement("textarea");
+ writer.writeAttribute("class", "console-textarea");
+ writer.writeAttribute("id", "pingTextArea");
+ writer.writeAttribute("readonly", "readonly");
+ writer.writeAttribute("rows", "12");
+ writer.writeCharacters("");
+ writer.writeEndElement(); // textarea
+
+
+ // Dig section
+ writer.writeEmptyElement("hr");
+ //: The ping section of the debug interface
+ writer.writeTextElement("h3", tr("DNS lookup for nymea.io"));
+ writer.writeEmptyElement("hr");
+
+ // Start dig button
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("id", "digButton");
+ writer.writeAttribute("onClick", "startDigTest()");
+ //: The ping button text of the debug interface
+ writer.writeCharacters(tr("Start DNS lookup test"));
+ writer.writeEndElement(); // button
+
+ // Dig output
+ writer.writeStartElement("textarea");
+ writer.writeAttribute("class", "console-textarea");
+ writer.writeAttribute("id", "digTextArea");
+ writer.writeAttribute("readonly", "readonly");
+ writer.writeAttribute("rows", "21");
+ writer.writeCharacters("");
+ writer.writeEndElement(); // textarea
+
+ // Dig section
+ writer.writeEmptyElement("hr");
+ //: The ping section of the debug interface
+ writer.writeTextElement("h3", tr("Trace path nymea.io"));
+ writer.writeEmptyElement("hr");
+
+ // Start tracepath button
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("id", "tracePathButton");
+ writer.writeAttribute("onClick", "startTracePathTest()");
+ //: The ping button text of the debug interface
+ writer.writeCharacters(tr("Start trace path test"));
+ writer.writeEndElement(); // button
+
+ // Dig output
+ writer.writeStartElement("textarea");
+ writer.writeAttribute("class", "console-textarea");
+ writer.writeAttribute("id", "tracePathTextArea");
+ writer.writeAttribute("readonly", "readonly");
+ writer.writeAttribute("rows", "20");
+ writer.writeCharacters("");
+ writer.writeEndElement(); // textarea
+
+ writer.writeEndElement(); // div network
+
+ // Logs stream
+ writer.writeStartElement("div");
+ writer.writeAttribute("class", "logstream");
+ writer.writeEmptyElement("hr");
+ //: The network section of the debug interface
+ writer.writeTextElement("h2", tr("Server debug log stream"));
+ writer.writeEmptyElement("hr");
+
+ // Start stream button
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("id", "connectWebsocketButton");
+ writer.writeAttribute("onClick", "connectWebsocket()");
+ //: The connect button for the log stream of the debug interface
+ writer.writeCharacters(tr("Connect stream"));
+ writer.writeEndElement(); // button
+
+ // Stop stream button
+ writer.writeStartElement("button");
+ writer.writeAttribute("class", "button");
+ writer.writeAttribute("type", "button");
+ writer.writeAttribute("id", "disconnectWebsocketButton");
+ writer.writeAttribute("onClick", "disconnectWebsocket()");
+ writer.writeAttribute("disabled", "true");
+ //: The disconnect button for the log stream of the debug interface
+ writer.writeCharacters(tr("Disconnect stream"));
+ writer.writeEndElement(); // button
+
+
+ // Dig output
+ writer.writeStartElement("textarea");
+ writer.writeAttribute("class", "console-textarea");
+ writer.writeAttribute("id", "logsTextArea");
+ writer.writeAttribute("readonly", "readonly");
+ writer.writeAttribute("rows", "30");
+ writer.writeCharacters("");
+ writer.writeEndElement(); // textarea
+
+
+ writer.writeEndElement(); // div body
// Footer
writer.writeStartElement("div");
@@ -573,290 +1193,120 @@ QByteArray DebugServerHandler::createErrorXmlDocument(HttpReply::HttpStatusCode
writer.writeEndElement(); // div footer
writer.writeEndElement(); // div container
-
writer.writeEndElement(); // html
return data;
}
-QByteArray DebugServerHandler::loadResourceFile(const QString &resourceFileName)
+void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
- QFile resourceFile(QString(":%1").arg(resourceFileName));
- if (!resourceFile.open(QFile::ReadOnly | QFile::Text)) {
- qCWarning(dcWebServer()) << "Could not open resource file" << resourceFile.fileName();
- return QByteArray();
+ QString messageString;
+ QString timeString = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz");
+ switch (type) {
+ case QtInfoMsg:
+ messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
+ fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
+ break;
+ case QtDebugMsg:
+ messageString = QString(" I %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
+ fprintf(stdout, " I | %s: %s\n", context.category, message.toUtf8().data());
+ break;
+ case QtWarningMsg:
+ messageString = QString(" W %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
+ fprintf(stderr, " W | %s: %s\n", context.category, message.toUtf8().data());
+ break;
+ case QtCriticalMsg:
+ messageString = QString(" C %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
+ fprintf(stderr, " C | %s: %s\n", context.category, message.toUtf8().data());
+ break;
+ case QtFatalMsg:
+ messageString = QString(" F %1 | %2: %3").arg(timeString).arg(context.category).arg(message);
+ fprintf(stderr, " F | %s: %s\n", context.category, message.toUtf8().data());
+ break;
}
+ fflush(stdout);
+ fflush(stderr);
- QTextStream inputStream(&resourceFile);
- return inputStream.readAll().toUtf8();
+ foreach (QWebSocket *client, DebugServerHandler::s_websocketClients) {
+ client->sendTextMessage(messageString + "\r\n");
+ }
}
-QString DebugServerHandler::getResourceFileName(const QString &requestPath)
+void DebugServerHandler::onTimeout()
{
- return QString(requestPath).remove("/debug");
+ foreach (QWebSocket *client, DebugServerHandler::s_websocketClients) {
+ client->sendTextMessage("Hallo!\n");
+ }
}
-bool DebugServerHandler::resourceFileExits(const QString &requestPath)
+void DebugServerHandler::onWebsocketClientConnected()
{
- QFile resourceFile(QString(":%1").arg(getResourceFileName(requestPath)));
- return resourceFile.exists();
+ QWebSocket *client = m_websocketServer->nextPendingConnection();
+ DebugServerHandler::s_websocketClients.append(client);
+ qCDebug(dcWebServer()) << "DebugServer: New websocket client connected:" << client->peerAddress().toString();
+
+ connect(client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onWebsocketClientError(QAbstractSocket::SocketError)));
+ connect(client, &QWebSocket::disconnected, this, &DebugServerHandler::onWebsocketClientDisconnected);
}
-HttpReply *DebugServerHandler::processDebugFileRequest(const QString &requestPath)
+void DebugServerHandler::onWebsocketClientDisconnected()
{
- // Here we already know that the resource file exists
- QString resourceFileName = getResourceFileName(requestPath);
- QByteArray data = loadResourceFile(resourceFileName);
-
- // Create reply for resource file
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setPayload(data);
-
- // Check content type
- if (resourceFileName.endsWith(".css")) {
- reply->setHeader(HttpReply::ContentTypeHeader, "text/css; charset=\"utf-8\";");
- } else if (resourceFileName.endsWith(".svg")) {
- reply->setHeader(HttpReply::ContentTypeHeader, "image/svg+xml; charset=\"utf-8\";");
- }
-
- return reply;
+ QWebSocket *client = static_cast(sender());
+ qCDebug(dcWebServer()) << "DebugServer: Websocket client disconnected" << client->peerAddress().toString();
+ DebugServerHandler::s_websocketClients.removeAll(client);
+ client->deleteLater();
}
-
-HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath)
+void DebugServerHandler::onWebsocketClientError(QAbstractSocket::SocketError error)
{
- qCDebug(dcWebServer()) << "Debug request for" << requestPath;
-
- // Check if debug page request
- if (requestPath == "/debug" || requestPath == "/debug/") {
- qCDebug(dcWebServer()) << "Create debug interface page";
- // Fallback default debug page
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createDebugXmlDocument());
- return reply;
- }
-
- // Check if this is a logdb requested
- if (requestPath.startsWith("/debug/logdb.sql")) {
- qCDebug(dcWebServer()) << "Loading" << NymeaCore::instance()->configuration()->logDBName();
- QFile logDatabaseFile(NymeaCore::instance()->configuration()->logDBName());
- if (!logDatabaseFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName() << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- //: The HTTP error message of the debug interface. The %1 represents the file name.
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(logDatabaseFile.fileName())));
- return reply;
- }
-
- if (!logDatabaseFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName();
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- //: The HTTP error message of the debug interface. The %1 represents the file name.
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(logDatabaseFile.fileName())));
- return reply;
- }
-
- QByteArray logDatabaseRawData = logDatabaseFile.readAll();
- logDatabaseFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "application/sql");
- reply->setPayload(logDatabaseRawData);
- return reply;
- }
-
-
- // Check if this is a syslog requested
- if (requestPath.startsWith("/debug/syslog")) {
- QString syslogFileName = "/var/log/syslog";
- qCDebug(dcWebServer()) << "Loading" << syslogFileName;
- QFile syslogFile(syslogFileName);
- if (!syslogFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read log database file for debug download" << syslogFileName << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(syslogFileName)));
- return reply;
- }
-
- if (!syslogFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read syslog file for debug download" << syslogFileName;
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(syslogFileName)));
- return reply;
- }
-
- QByteArray syslogFileData = syslogFile.readAll();
- syslogFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
- reply->setPayload(syslogFileData);
- return reply;
- }
-
- // Check if this is a settings request
- if (requestPath.startsWith("/debug/settings")) {
- if (requestPath.startsWith("/debug/settings/devices")) {
- QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleDevices).fileName();
- qCDebug(dcWebServer()) << "Loading" << settingsFileName;
- QFile settingsFile(settingsFileName);
- if (!settingsFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- if (!settingsFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName;
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- QByteArray settingsFileData = settingsFile.readAll();
- settingsFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
- reply->setPayload(settingsFileData);
- return reply;
- }
-
- if (requestPath.startsWith("/debug/settings/rules")) {
- QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleRules).fileName();
- qCDebug(dcWebServer()) << "Loading" << settingsFileName;
- QFile settingsFile(settingsFileName);
- if (!settingsFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- if (!settingsFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName;
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- QByteArray settingsFileData = settingsFile.readAll();
- settingsFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
- reply->setPayload(settingsFileData);
- return reply;
- }
-
- if (requestPath.startsWith("/debug/settings/nymead")) {
- QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName();
- qCDebug(dcWebServer()) << "Loading" << settingsFileName;
- QFile settingsFile(settingsFileName);
- if (!settingsFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- if (!settingsFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName;
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- QByteArray settingsFileData = settingsFile.readAll();
- settingsFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
- reply->setPayload(settingsFileData);
- return reply;
- }
-
- if (requestPath.startsWith("/debug/settings/devicestates")) {
- QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).fileName();
- qCDebug(dcWebServer()) << "Loading" << settingsFileName;
- QFile settingsFile(settingsFileName);
- if (!settingsFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- if (!settingsFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName;
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- QByteArray settingsFileData = settingsFile.readAll();
- settingsFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
- reply->setPayload(settingsFileData);
- return reply;
- }
-
- if (requestPath.startsWith("/debug/settings/plugins")) {
- QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRolePlugins).fileName();
- qCDebug(dcWebServer()) << "Loading" << settingsFileName;
- QFile settingsFile(settingsFileName);
- if (!settingsFile.exists()) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- if (!settingsFile.open(QFile::ReadOnly)) {
- qCWarning(dcWebServer()) << "Could not read file for debug download" << settingsFileName;
- HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
- reply->setHeader(HttpReply::ContentTypeHeader, "text/html");
- reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName)));
- return reply;
- }
-
- QByteArray settingsFileData = settingsFile.readAll();
- settingsFile.close();
-
- HttpReply *reply = RestResource::createSuccessReply();
- reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
- reply->setPayload(settingsFileData);
- return reply;
- }
- }
-
- // Check if this is a resource file request
- if (resourceFileExits(requestPath)) {
- return processDebugFileRequest(requestPath);
- }
-
- // If nothing matches, redirect to /debug page
- qCWarning(dcWebServer()) << "Resource for debug interface not found. Redirecting to /debug";
- HttpReply *reply = RestResource::createErrorReply(HttpReply::PermanentRedirect);
- reply->setHeader(HttpReply::LocationHeader, "/debug");
- return reply;
+ QWebSocket *client = static_cast(sender());
+ qCWarning(dcWebServer()) << "DebugServer: Websocket client error" << client->peerAddress().toString() << error << client->errorString();
}
+void DebugServerHandler::onPingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ qCDebug(dcWebServer()) << "DebugServer: Ping process finished" << exitCode << exitStatus;
+ QByteArray processOutput = m_pingProcess->readAll();
+ qCDebug(dcWebServer()) << "DebugServer: Ping output:" << endl << qUtf8Printable(processOutput);
+
+ m_pingReply->setPayload(processOutput);
+ m_pingReply->setHttpStatusCode(HttpReply::Ok);
+ m_pingReply->finished();
+ m_pingReply = nullptr;
+
+ m_pingProcess->deleteLater();
+ m_pingProcess = nullptr;
+}
+
+void DebugServerHandler::onDigProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ qCDebug(dcWebServer()) << "DebugServer: Dig process finished" << exitCode << exitStatus;
+ QByteArray processOutput = m_digProcess->readAll();
+ qCDebug(dcWebServer()) << "DebugServer: Dig output:" << endl << qUtf8Printable(processOutput);
+
+ m_digReply->setPayload(processOutput);
+ m_digReply->setHttpStatusCode(HttpReply::Ok);
+ m_digReply->finished();
+ m_digReply = nullptr;
+
+ m_digProcess->deleteLater();
+ m_digProcess = nullptr;
+}
+
+void DebugServerHandler::onTracePathProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ qCDebug(dcWebServer()) << "DebugServer: Tracepath process finished" << exitCode << exitStatus;
+ QByteArray processOutput = m_tracePathProcess->readAll();
+ qCDebug(dcWebServer()) << "DebugServer: Tracepath output:" << endl << qUtf8Printable(processOutput);
+
+ m_tracePathReply->setPayload(processOutput);
+ m_tracePathReply->setHttpStatusCode(HttpReply::Ok);
+ m_tracePathReply->finished();
+ m_tracePathReply = nullptr;
+
+ m_tracePathProcess->deleteLater();
+ m_tracePathProcess = nullptr;
+}
+
+
}
diff --git a/libnymea-core/debugserverhandler.h b/libnymea-core/debugserverhandler.h
index 3de2a46d..8260f493 100644
--- a/libnymea-core/debugserverhandler.h
+++ b/libnymea-core/debugserverhandler.h
@@ -21,7 +21,10 @@
#ifndef DEBUGSERVERHANDLER_H
#define DEBUGSERVERHANDLER_H
+#include
#include
+#include
+#include
#include "httpreply.h"
@@ -35,15 +38,41 @@ public:
HttpReply *processDebugRequest(const QString &requestPath);
+ static QList s_websocketClients;
+ static void consoleLogHandler(QtMsgType type, const QMessageLogContext& context, const QString& message);
+
private:
- QByteArray createDebugXmlDocument();
- QByteArray createErrorXmlDocument(HttpReply::HttpStatusCode statusCode, const QString &errorMessage);
- QByteArray loadResourceFile(const QString &resourceFileName);
+ QTimer *m_timer = nullptr;
+ QWebSocketServer *m_websocketServer = nullptr;
+
+ QProcess *m_pingProcess = nullptr;
+ HttpReply *m_pingReply = nullptr;
+
+ QProcess *m_digProcess = nullptr;
+ HttpReply *m_digReply = nullptr;
+
+ QProcess *m_tracePathProcess = nullptr;
+ HttpReply *m_tracePathReply = nullptr;
+
+ QByteArray loadResourceData(const QString &resourceFileName);
QString getResourceFileName(const QString &requestPath);
bool resourceFileExits(const QString &requestPath);
HttpReply *processDebugFileRequest(const QString &requestPath);
+ QByteArray createDebugXmlDocument();
+ QByteArray createErrorXmlDocument(HttpReply::HttpStatusCode statusCode, const QString &errorMessage);
+
+private slots:
+ void onTimeout();
+ void onWebsocketClientConnected();
+ void onWebsocketClientDisconnected();
+ void onWebsocketClientError(QAbstractSocket::SocketError error);
+
+ void onPingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void onDigProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void onTracePathProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
+
};
}
diff --git a/libnymea-core/httpreply.cpp b/libnymea-core/httpreply.cpp
index 8a9913f4..3635c424 100644
--- a/libnymea-core/httpreply.cpp
+++ b/libnymea-core/httpreply.cpp
@@ -417,7 +417,7 @@ QByteArray HttpReply::getHeaderType(const HttpReply::HttpHeaderType &headerType)
*/
void HttpReply::startWait()
{
- m_timer->start(10000);
+ m_timer->start(60000);
}
void HttpReply::timeout()
diff --git a/libnymea-core/webserver.cpp b/libnymea-core/webserver.cpp
index cce62165..3867a6ad 100644
--- a/libnymea-core/webserver.cpp
+++ b/libnymea-core/webserver.cpp
@@ -132,7 +132,7 @@ QUrl WebServer::serverUrl() const
void WebServer::sendHttpReply(HttpReply *reply)
{
// get the right socket
- QSslSocket *socket = 0;
+ QSslSocket *socket = nullptr;
socket = m_clientList.value(reply->clientId());
if (!socket) {
qCWarning(dcWebServer) << "Invalid socket pointer! This should never happen!!! Missing clientId in reply?";
@@ -141,7 +141,7 @@ void WebServer::sendHttpReply(HttpReply *reply)
// send raw data
reply->packReply();
- qCDebug(dcWebServer) << "respond" << reply->httpStatusCode() << reply->httpReasonPhrase();
+ qCDebug(dcWebServer) << "Respond" << reply->httpStatusCode() << reply->httpReasonPhrase();
socket->write(reply->data());
}
@@ -161,7 +161,7 @@ bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName)
// make sure the file is in the public directory
if (!file.canonicalFilePath().startsWith(QDir(m_configuration.publicFolder).canonicalPath())) {
- qCWarning(dcWebServer) << "requested file" << file.fileName() << "is outside the public folder.";
+ qCWarning(dcWebServer) << "Requested file" << file.fileName() << "is outside the public folder.";
HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
reply->setClientId(m_clientList.key(socket));
sendHttpReply(reply);
@@ -171,7 +171,7 @@ bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName)
// make sure we can read the file
if (!file.isReadable()) {
- qCWarning(dcWebServer) << "requested file" << file.fileName() << "is not readable.";
+ qCWarning(dcWebServer) << "Requested file" << file.fileName() << "is not readable.";
HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden);
reply->setClientId(m_clientList.key(socket));
reply->setPayload("403 Forbidden. File not readable");
@@ -388,8 +388,15 @@ void WebServer::readClient()
HttpReply *reply = NymeaCore::instance()->debugServerHandler()->processDebugRequest(request.url().path());
reply->setClientId(clientId);
- sendHttpReply(reply);
- reply->deleteLater();
+
+ // Handle async replies
+ if (reply->type() == HttpReply::TypeAsync) {
+ connect(reply, &HttpReply::finished, this, &WebServer::onAsyncReplyFinished);
+ reply->startWait();
+ } else {
+ sendHttpReply(reply);
+ reply->deleteLater();
+ }
return;
} else {
qCWarning(dcWebServer()) << "The debug server handler is disabled. You can enable it by adding \'debugServerEnabled=true\' in the \'nymead\' section of the nymead.conf file.";
@@ -403,7 +410,7 @@ void WebServer::readClient()
// Check server.xml call
if (request.url().path() == "/server.xml" && request.method() == HttpRequest::Get) {
- qCDebug(dcWebServer) << "server XML request call";
+ qCDebug(dcWebServer) << "Server XML request call";
HttpReply *reply = RestResource::createSuccessReply();
reply->setHeader(HttpReply::ContentTypeHeader, "text/xml");
reply->setPayload(createServerXmlDocument(socket->localAddress()));
@@ -418,7 +425,7 @@ void WebServer::readClient()
if (request.method() == HttpRequest::Get) {
// Check if the webinterface dir does exist, otherwise a filerequest is not relevant
if (!QDir(m_configuration.publicFolder).exists()) {
- qCWarning(dcWebServer) << "webinterface folder" << m_configuration.publicFolder << "does not exist.";
+ qCWarning(dcWebServer) << "Webinterface folder" << m_configuration.publicFolder << "does not exist.";
HttpReply *reply = RestResource::createErrorReply(HttpReply::NotFound);
reply->setClientId(clientId);
sendHttpReply(reply);
@@ -432,7 +439,7 @@ void WebServer::readClient()
QFile file(path);
if (file.open(QFile::ReadOnly | QFile::Truncate)) {
- qCDebug(dcWebServer) << "load file" << file.fileName();
+ qCDebug(dcWebServer) << "Load file" << file.fileName();
HttpReply *reply = RestResource::createSuccessReply();
// Check content type
@@ -522,6 +529,21 @@ void WebServer::onError(QAbstractSocket::SocketError error)
qCWarning(dcWebServer()) << QString("Client socket error %1:%2 ->").arg(socket->peerAddress().toString()).arg(socket->peerPort()) << socket->errorString();
}
+void WebServer::onAsyncReplyFinished()
+{
+ HttpReply *reply = qobject_cast(sender());
+ qCDebug(dcWebServer) << "Async reply finished";
+
+ // check if the reply timeouted
+ if (reply->timedOut()) {
+ reply->clear();
+ reply->setHttpStatusCode(HttpReply::GatewayTimeout);
+ }
+
+ sendHttpReply(reply);
+ reply->deleteLater();
+}
+
void WebServer::onAvahiServiceStateChanged(const QtAvahiService::QtAvahiServiceState &state)
{
Q_UNUSED(state)
@@ -826,7 +848,7 @@ void WebServerClient::removeConnection(QSslSocket *socket)
*/
void WebServerClient::resetTimout(QSslSocket *socket)
{
- QTimer *timer = 0;
+ QTimer *timer = nullptr;
timer = m_runningConnections.key(socket);
if (timer)
timer->start();
diff --git a/libnymea-core/webserver.h b/libnymea-core/webserver.h
index 894a5bf8..1d9a31e8 100644
--- a/libnymea-core/webserver.h
+++ b/libnymea-core/webserver.h
@@ -113,6 +113,7 @@ private slots:
void onDisconnected();
void onEncrypted();
void onError(QAbstractSocket::SocketError error);
+ void onAsyncReplyFinished();
void onAvahiServiceStateChanged(const QtAvahiService::QtAvahiServiceState &state);
void resetAvahiService();