Add network test functionality and add live log viewer for debug server interface

This commit is contained in:
Simon Stürz 2018-11-13 22:36:43 +01:00 committed by Michael Zanetti
parent d3f1e199f2
commit 4bd3b836f8
28 changed files with 1140 additions and 387 deletions

View File

@ -1,8 +1,15 @@
<RCC>
<qresource prefix="">
<qresource prefix="/">
<file>logo.svg</file>
<file>warning.svg</file>
<file>script.js</file>
<file>styles.css</file>
<file>favicons/favicon-16x16.png</file>
<file>favicons/favicon-32x32.png</file>
<file>favicons/favicon-96x96.png</file>
<file>favicons/favicon-128.png</file>
<file>favicons/favicon-196x196.png</file>
<file>favicons/favicon.ico</file>
<file>favicons/favicon.svg</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,152 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* ========================================================================*/
/* 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
}
};
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,10 @@
#ifndef DEBUGSERVERHANDLER_H
#define DEBUGSERVERHANDLER_H
#include <QTimer>
#include <QObject>
#include <QProcess>
#include <QWebSocketServer>
#include "httpreply.h"
@ -35,15 +38,41 @@ public:
HttpReply *processDebugRequest(const QString &requestPath);
static QList<QWebSocket *> 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);
};
}

View File

@ -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()

View File

@ -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<HttpReply*>(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();

View File

@ -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();