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 @@ + + + +image/svg+xml \ 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();