mirror of https://github.com/nymea/nymea.git
1994 lines
87 KiB
C++
1994 lines
87 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||
*
|
||
* Copyright 2013 - 2020, nymea GmbH
|
||
* Contact: contact@nymea.io
|
||
*
|
||
* This file is part of nymea.
|
||
* This project including source code and documentation is protected by
|
||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||
* reproduction, publication, editing and translation, are reserved. The use of
|
||
* this project is subject to the terms of a license agreement to be concluded
|
||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||
* under https://nymea.io/license
|
||
*
|
||
* GNU General Public License Usage
|
||
* Alternatively, this project may be redistributed and/or modified under the
|
||
* terms of the GNU General Public License as published by the Free Software
|
||
* Foundation, GNU version 3. This project 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
|
||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||
*
|
||
* For any further details and any questions please contact us under
|
||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||
* https://nymea.io/license/faq
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#include "nymeacore.h"
|
||
#include "servers/httprequest.h"
|
||
#include "servers/httpreply.h"
|
||
#include "nymeasettings.h"
|
||
#include "loggingcategories.h"
|
||
#include "debugserverhandler.h"
|
||
#include "nymeaconfiguration.h"
|
||
#include "stdio.h"
|
||
#include "version.h"
|
||
|
||
#include <QXmlStreamWriter>
|
||
#include <QCoreApplication>
|
||
#include <QMessageLogger>
|
||
#include <QJsonDocument>
|
||
#include <QXmlStreamWriter>
|
||
#include <QCoreApplication>
|
||
#include <QFileInfo>
|
||
#include <QWebSocket>
|
||
#include <QPair>
|
||
#include <QHostInfo>
|
||
|
||
|
||
namespace nymeaserver {
|
||
|
||
QList<QWebSocket*> DebugServerHandler::s_websocketClients;
|
||
QMutex DebugServerHandler::s_loggingMutex;
|
||
|
||
DebugServerHandler::DebugServerHandler(QObject *parent) :
|
||
QObject(parent)
|
||
{
|
||
connect(NymeaCore::instance()->configuration(), &NymeaConfiguration::debugServerEnabledChanged, this, &DebugServerHandler::onDebugServerEnabledChanged);
|
||
onDebugServerEnabledChanged(NymeaCore::instance()->configuration()->debugServerEnabled());
|
||
}
|
||
|
||
HttpReply *DebugServerHandler::processDebugRequest(const QString &requestPath, const QUrlQuery &requestQuery)
|
||
{
|
||
qCDebug(dcDebugServer()) << "Debug request for" << requestPath;
|
||
|
||
// Check if debug page request
|
||
if (requestPath == "/debug" || requestPath == "/debug/") {
|
||
qCDebug(dcDebugServer()) << "Create debug interface page";
|
||
// Fallback default debug page
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Loading" << NymeaCore::instance()->configuration()->logDBName();
|
||
QFile logDatabaseFile(NymeaCore::instance()->configuration()->logDBName());
|
||
if (!logDatabaseFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName() << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName();
|
||
HttpReply *reply = HttpReply::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 = HttpReply::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(dcDebugServer()) << "Loading" << syslogFileName;
|
||
QFile syslogFile(syslogFileName);
|
||
if (!syslogFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read log database file for debug download" << syslogFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read syslog file for debug download" << syslogFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::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/things")) {
|
||
QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleThings).fileName();
|
||
qCDebug(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::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(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::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(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::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(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::createSuccessReply();
|
||
reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
|
||
reply->setPayload(settingsFileData);
|
||
return reply;
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/settings/tags")) {
|
||
QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleTags).fileName();
|
||
qCDebug(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::createSuccessReply();
|
||
reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
|
||
reply->setPayload(settingsFileData);
|
||
return reply;
|
||
}
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/settings/mqttpolicies")) {
|
||
QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleMqttPolicies).fileName();
|
||
qCDebug(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::createSuccessReply();
|
||
reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
|
||
reply->setPayload(settingsFileData);
|
||
return reply;
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/settings/ioconnections")) {
|
||
QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleIOConnections).fileName();
|
||
qCDebug(dcDebugServer()) << "Loading" << settingsFileName;
|
||
QFile settingsFile(settingsFileName);
|
||
if (!settingsFile.exists()) {
|
||
qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName << "file does not exist.";
|
||
HttpReply *reply = HttpReply::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(dcDebugServer()) << "Could not read file for debug download" << settingsFileName;
|
||
HttpReply *reply = HttpReply::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 = HttpReply::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 HttpReply::createErrorReply(HttpReply::InternalServerError);
|
||
|
||
qCDebug(dcDebugServer()) << "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 = HttpReply::createAsyncReply();
|
||
connect(m_pingReply, &HttpReply::finished, this, [this](){
|
||
m_pingReply = nullptr;
|
||
});
|
||
return m_pingReply;
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/dig")) {
|
||
// Only one dig process should run
|
||
if (m_digProcess || m_digReply)
|
||
return HttpReply::createErrorReply(HttpReply::InternalServerError);
|
||
|
||
qCDebug(dcDebugServer()) << "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 = HttpReply::createAsyncReply();
|
||
connect(m_digReply, &HttpReply::finished, this, [this](){
|
||
m_digReply = nullptr;
|
||
});
|
||
return m_digReply;
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/tracepath")) {
|
||
// Only one tracepath process should run
|
||
if (m_tracePathProcess || m_tracePathReply)
|
||
return HttpReply::createErrorReply(HttpReply::InternalServerError);
|
||
|
||
qCDebug(dcDebugServer()) << "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 = HttpReply::createAsyncReply();
|
||
connect(m_tracePathReply, &HttpReply::finished, this, [this](){
|
||
m_tracePathReply = nullptr;
|
||
});
|
||
return m_tracePathReply;
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/logging-categories")) {
|
||
|
||
if (requestQuery.isEmpty()) {
|
||
// Return the list of debug category settings
|
||
NymeaSettings settings(NymeaSettings::SettingsRoleGlobal);
|
||
settings.beginGroup("LoggingRules");
|
||
|
||
qCDebug(dcDebugServer()) << "Request logging categories list";
|
||
QVariantMap dataMap;
|
||
QVariantMap loggingCategories;
|
||
foreach (const QString &loggingCategory, NymeaCore::loggingFilters()) {
|
||
QString level = "critical";
|
||
if (settings.value(QString("%1.warning").arg(loggingCategory), true).toBool()) {
|
||
level = "warning";
|
||
}
|
||
if (settings.value(QString("%1.info").arg(loggingCategory), true).toBool()) {
|
||
level = "info";
|
||
}
|
||
if (settings.value(QString("%1.debug").arg(loggingCategory), false).toBool()) {
|
||
level = "debug";
|
||
}
|
||
loggingCategories.insert(loggingCategory, level);
|
||
}
|
||
dataMap.insert("loggingCategories", loggingCategories);
|
||
QVariantMap loggingCategoriesPlugins;
|
||
foreach (const QString &loggingCategory, NymeaCore::loggingFiltersPlugins()) {
|
||
QString level = "critical";
|
||
if (settings.value(QString("%1.warning").arg(loggingCategory), true).toBool()) {
|
||
level = "warning";
|
||
}
|
||
if (settings.value(QString("%1.info").arg(loggingCategory), true).toBool()) {
|
||
level = "info";
|
||
}
|
||
if (settings.value(QString("%1.debug").arg(loggingCategory), false).toBool()) {
|
||
level = "debug";
|
||
}
|
||
loggingCategoriesPlugins.insert(loggingCategory, level);
|
||
}
|
||
dataMap.insert("loggingCategoriesPlugins", loggingCategoriesPlugins);
|
||
|
||
settings.endGroup();
|
||
|
||
HttpReply *reply = HttpReply::createSuccessReply();
|
||
reply->setPayload(QJsonDocument::fromVariant(dataMap).toJson(QJsonDocument::Indented));
|
||
return reply;
|
||
} else {
|
||
NymeaSettings settings(NymeaSettings::SettingsRoleGlobal);
|
||
settings.beginGroup("LoggingRules");
|
||
for (int i = 0; i < requestQuery.queryItems().count(); i++) {
|
||
QString category = requestQuery.queryItems().at(i).first;
|
||
if (!NymeaCore::loggingFilters().contains(category) && !NymeaCore::loggingFiltersPlugins().contains(category)) {
|
||
qCWarning(dcDebugServer()) << "Invalid logging category in request query" << requestQuery.toString() << category;
|
||
continue;
|
||
}
|
||
|
||
QString level = QVariant(requestQuery.queryItems().at(i).second).toString();
|
||
qCDebug(dcDebugServer()) << "Logging category" << category << level;
|
||
if (level == "debug") {
|
||
settings.setValue(QString("%1.debug").arg(category), true);
|
||
settings.setValue(QString("%1.info").arg(category), true);
|
||
settings.setValue(QString("%1.warning").arg(category), true);
|
||
} else if (level == "info") {
|
||
settings.setValue(QString("%1.debug").arg(category), false);
|
||
settings.setValue(QString("%1.info").arg(category), true);
|
||
settings.setValue(QString("%1.warning").arg(category), true);
|
||
} else if (level == "warning"){
|
||
settings.setValue(QString("%1.debug").arg(category), false);
|
||
settings.setValue(QString("%1.info").arg(category), false);
|
||
settings.setValue(QString("%1.warning").arg(category), true);
|
||
} else {
|
||
settings.setValue(QString("%1.debug").arg(category), false);
|
||
settings.setValue(QString("%1.info").arg(category), false);
|
||
settings.setValue(QString("%1.warning").arg(category), false);
|
||
}
|
||
}
|
||
|
||
// Update logging filter rules according to the nw settings
|
||
QStringList loggingRules;
|
||
loggingRules << "*.debug=false";
|
||
// Load the rules from nymead.conf file and append them to the rules
|
||
foreach (const QString &category, settings.childKeys()) {
|
||
loggingRules << QString("%1=%2").arg(category).arg(settings.value(category, "false").toString());
|
||
}
|
||
settings.endGroup();
|
||
QLoggingCategory::setFilterRules(loggingRules.join('\n'));
|
||
|
||
return HttpReply::createSuccessReply();
|
||
}
|
||
}
|
||
|
||
if (requestPath.startsWith("/debug/report")) {
|
||
|
||
// The client can poll this url in order to get information about the current report generating process.
|
||
// If there is currently no report generated, start generating it and inform client that there is a report on the way (204)
|
||
// If there is already a report generation in progress, inform the client that it's not ready yet (204)
|
||
// If the report is ready, return information of the file and the client will start downloading it (202 and file informations).
|
||
|
||
// Check if download request or generate request
|
||
if (requestQuery.hasQueryItem("filename")) {
|
||
QString fileName = requestQuery.queryItemValue("filename");
|
||
qCDebug(dcDebugServer()) << "Report download request for" << fileName;
|
||
|
||
if (!m_debugReportGenerator) {
|
||
qCWarning(dcDebugServer()) << "There is currently no debug report generator. The requested file does not exist.";
|
||
return HttpReply::createErrorReply(HttpReply::NotFound);
|
||
}
|
||
|
||
if (m_debugReportGenerator->reportFileName() != fileName) {
|
||
qCWarning(dcDebugServer()) << "The requested file is not the file from the current debug report generator" << m_debugReportGenerator->reportFileName() << "!=" << fileName;
|
||
return HttpReply::createErrorReply(HttpReply::NotFound);
|
||
|
||
}
|
||
|
||
// Everything looks good, send the requested debug report
|
||
HttpReply *downloadReportReply = HttpReply::createSuccessReply();
|
||
downloadReportReply->setPayload(m_debugReportGenerator->reportFileData());
|
||
downloadReportReply->setHeader(HttpReply::ContentTypeHeader, "application/tar+gzip;");
|
||
return downloadReportReply;
|
||
} else {
|
||
// Generate or poll request
|
||
if (!m_debugReportGenerator) {
|
||
qCDebug(dcDebugServer()) << "Create new debug report generator and start generating report...";
|
||
m_debugReportGenerator = new DebugReportGenerator(this);
|
||
connect(m_debugReportGenerator, &DebugReportGenerator::finished, this, &DebugServerHandler::onDebugReportGeneratorFinished);
|
||
connect(m_debugReportGenerator, &DebugReportGenerator::timeout, this, &DebugServerHandler::onDebugReportGeneratorTimeout);
|
||
m_debugReportGenerator->generateReport();
|
||
// Note: no content will bring the client to poll this report
|
||
return HttpReply::createErrorReply(HttpReply::NoContent);
|
||
} else {
|
||
// There is a running generator, check if the report is ready
|
||
if (!m_debugReportGenerator->isReady()) {
|
||
qCDebug(dcDebugServer()) << "Report is not ready yet";
|
||
// Note: no content tells the client the report is not ready yet
|
||
return HttpReply::createErrorReply(HttpReply::NoContent);
|
||
} else {
|
||
if (m_debugReportGenerator->isValid()) {
|
||
// Success, the debug report is ready and valid
|
||
QVariantMap reportInformation;
|
||
reportInformation.insert("fileName", m_debugReportGenerator->reportFileName());
|
||
reportInformation.insert("fileSize", m_debugReportGenerator->reportFileData().size());
|
||
reportInformation.insert("md5sum", m_debugReportGenerator->md5Sum());
|
||
|
||
HttpReply * httpReply = HttpReply::createSuccessReply();
|
||
httpReply->setHttpStatusCode(HttpReply::Ok);
|
||
httpReply->setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
|
||
httpReply->setPayload(QJsonDocument::fromVariant(reportInformation).toJson(QJsonDocument::Indented));
|
||
return httpReply;
|
||
} else {
|
||
qCWarning(dcDebugServer()) << "The debug report generator finished with error.";
|
||
m_debugReportGenerator->deleteLater();
|
||
m_debugReportGenerator = nullptr;
|
||
return HttpReply::createErrorReply(HttpReply::InternalServerError);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check if this is a resource file request
|
||
if (resourceFileExits(requestPath)) {
|
||
return processDebugFileRequest(requestPath);
|
||
}
|
||
|
||
// If nothing matches, redirect to /debug page
|
||
qCWarning(dcDebugServer()) << "Resource for debug interface not found. Redirecting to /debug";
|
||
HttpReply *reply = HttpReply::createErrorReply(HttpReply::PermanentRedirect);
|
||
reply->setHeader(HttpReply::LocationHeader, "/debug");
|
||
return reply;
|
||
}
|
||
|
||
void DebugServerHandler::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||
{
|
||
QString finalMessage;
|
||
switch (type) {
|
||
case QtDebugMsg:
|
||
finalMessage = QString(" I | %1: %2\n").arg(context.category).arg(message);
|
||
break;
|
||
case QtInfoMsg:
|
||
finalMessage = QString(" I | %1: %2\n").arg(context.category).arg(message);
|
||
break;
|
||
case QtWarningMsg:
|
||
finalMessage = QString(" W | %1: %2\n").arg(context.category).arg(message);
|
||
break;
|
||
case QtCriticalMsg:
|
||
finalMessage = QString(" C | %1: %2\n").arg(context.category).arg(message);
|
||
break;
|
||
case QtFatalMsg:
|
||
finalMessage = QString(" F | %1: %2\n").arg(context.category).arg(message);
|
||
break;
|
||
}
|
||
|
||
QMutexLocker locker(&s_loggingMutex);
|
||
foreach (QWebSocket *client, s_websocketClients) {
|
||
client->sendTextMessage(finalMessage);
|
||
}
|
||
}
|
||
|
||
QByteArray DebugServerHandler::loadResourceData(const QString &resourceFileName)
|
||
{
|
||
QFile resourceFile(QString(":%1").arg(resourceFileName));
|
||
if (!resourceFile.open(QFile::ReadOnly)) {
|
||
qCWarning(dcDebugServer()) << "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 = HttpReply::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;
|
||
}
|
||
|
||
void DebugServerHandler::onDebugServerEnabledChanged(bool enabled)
|
||
{
|
||
if (enabled) {
|
||
m_websocketServer = new QWebSocketServer("Debug server", QWebSocketServer::NonSecureMode, this);
|
||
connect(m_websocketServer, &QWebSocketServer::newConnection, this, &DebugServerHandler::onWebsocketClientConnected);
|
||
if (!m_websocketServer->listen(QHostAddress::Any, 2626)) {
|
||
qCWarning(dcDebugServer()) << "The debug server websocket interface could not listen on" << m_websocketServer->serverUrl().toString();
|
||
m_websocketServer->deleteLater();
|
||
m_websocketServer = nullptr;
|
||
return;
|
||
}
|
||
|
||
qCDebug(dcDebugServer()) << "The debug server websocket interface has been started on" << m_websocketServer->serverUrl().toString();
|
||
} else {
|
||
if (m_websocketServer) {
|
||
m_websocketServer->close();
|
||
qCDebug(dcDebugServer()) << "The debug server websocket interface has been closed" << m_websocketServer->serverUrl().toString();
|
||
m_websocketServer->deleteLater();
|
||
m_websocketServer = nullptr;
|
||
}
|
||
}
|
||
}
|
||
|
||
void DebugServerHandler::onWebsocketClientConnected()
|
||
{
|
||
QWebSocket *client = m_websocketServer->nextPendingConnection();
|
||
|
||
if (s_websocketClients.isEmpty()) {
|
||
qCDebug(dcDebugServer()) << "Install debug message handler for live logs.";
|
||
//QLoggingCategory::setFilterRules("*.debug=true");
|
||
nymeaInstallMessageHandler(&logMessageHandler);
|
||
}
|
||
|
||
s_websocketClients.append(client);
|
||
qCDebug(dcDebugServer()) << "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);
|
||
}
|
||
|
||
void DebugServerHandler::onWebsocketClientDisconnected()
|
||
{
|
||
QWebSocket *client = static_cast<QWebSocket *>(sender());
|
||
qCDebug(dcDebugServer()) << "Websocket client disconnected" << client->peerAddress().toString();
|
||
s_websocketClients.removeAll(client);
|
||
client->deleteLater();
|
||
|
||
if (s_websocketClients.isEmpty()) {
|
||
qCDebug(dcDebugServer()) << "Uninstalling debug message handler for live logs.";
|
||
nymeaUninstallMessageHandler(&logMessageHandler);
|
||
}
|
||
}
|
||
|
||
void DebugServerHandler::onWebsocketClientError(QAbstractSocket::SocketError error)
|
||
{
|
||
QWebSocket *client = static_cast<QWebSocket *>(sender());
|
||
qCWarning(dcDebugServer()) << "Websocket client error" << client->peerAddress().toString() << error << client->errorString();
|
||
}
|
||
|
||
void DebugServerHandler::onPingProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||
{
|
||
qCDebug(dcDebugServer()) << "Ping process finished" << exitCode << exitStatus;
|
||
QByteArray processOutput = m_pingProcess->readAll();
|
||
qCDebug(dcDebugServer()) << "Ping output:" << endl << qUtf8Printable(processOutput);
|
||
|
||
if (m_pingReply) {
|
||
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(dcDebugServer()) << "Dig process finished" << exitCode << exitStatus;
|
||
QByteArray processOutput = m_digProcess->readAll();
|
||
qCDebug(dcDebugServer()) << "Dig output:" << endl << qUtf8Printable(processOutput);
|
||
|
||
if (m_digReply) {
|
||
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(dcDebugServer()) << "Tracepath process finished" << exitCode << exitStatus;
|
||
QByteArray processOutput = m_tracePathProcess->readAll();
|
||
qCDebug(dcDebugServer()) << "Tracepath output:" << endl << qUtf8Printable(processOutput);
|
||
|
||
if (m_tracePathReply) {
|
||
m_tracePathReply->setPayload(processOutput);
|
||
m_tracePathReply->setHttpStatusCode(HttpReply::Ok);
|
||
m_tracePathReply->finished();
|
||
m_tracePathReply = nullptr;
|
||
}
|
||
|
||
m_tracePathProcess->deleteLater();
|
||
m_tracePathProcess = nullptr;
|
||
}
|
||
|
||
void DebugServerHandler::onDebugReportGeneratorFinished(bool success)
|
||
{
|
||
DebugReportGenerator *debugReportGenerator = static_cast<DebugReportGenerator *>(sender());
|
||
qCDebug(dcDebugServer()) << "Report generation finished" << (success ? "successfully" : "with error") << debugReportGenerator->reportFileName();
|
||
}
|
||
|
||
void DebugServerHandler::onDebugReportGeneratorTimeout()
|
||
{
|
||
qCWarning(dcDebugServer()) << "Debug report expired.";
|
||
if (m_debugReportGenerator) {
|
||
m_debugReportGenerator->deleteLater();
|
||
m_debugReportGenerator = nullptr;
|
||
}
|
||
}
|
||
|
||
QByteArray DebugServerHandler::createDebugXmlDocument()
|
||
{
|
||
QByteArray data;
|
||
QXmlStreamWriter writer(&data);
|
||
writer.setAutoFormatting(true);
|
||
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());
|
||
|
||
// Head
|
||
writer.writeStartElement("head");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("http-equiv", "Content-Type");
|
||
writer.writeAttribute("content", "text/html; charset=utf-8");
|
||
|
||
writer.writeEmptyElement("link");
|
||
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
|
||
|
||
// Default 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", "96x96");
|
||
writer.writeAttribute("href", "/debug/favicons/favicon-96x96.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");
|
||
|
||
// Apple favicons
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "57x57");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-57x57.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "60x60");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-60x60.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "72x72");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-72x72.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "76x76");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-76x76.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "114x114");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-114x114.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "120x120");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-144x144.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "144x144");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-144x144.png");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "apple-touch-icon-precomposed");
|
||
writer.writeAttribute("sizes", "152x152");
|
||
writer.writeAttribute("href", "/debug/favicons/apple-touch-icon-152x152.png");
|
||
|
||
// Microsoft favicons
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "nymea");
|
||
writer.writeAttribute("content", " ");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "msapplication-TileColor");
|
||
writer.writeAttribute("content", "#FFFFFF");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "msapplication-TileImage");
|
||
writer.writeAttribute("content", "/debug/favicons/mstile-144x144.png");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "msapplication-square70x70logo");
|
||
writer.writeAttribute("content", "/debug/favicons/mstile-70x70.png");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "msapplication-square150x150logo");
|
||
writer.writeAttribute("content", "/debug/favicons/mstile-150x150.png");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "msapplication-wide310x150logo");
|
||
writer.writeAttribute("content", "/debug/favicons/mstile-310x150.png");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("name", "msapplication-square310x310logo");
|
||
writer.writeAttribute("content", "/debug/favicons/mstile-310x310.png");
|
||
|
||
//: The header title of the debug server interface
|
||
writer.writeTextElement("title", tr("Debug nymea"));
|
||
|
||
writer.writeEndElement(); // head
|
||
|
||
// Container
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "container");
|
||
|
||
// Header
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "header");
|
||
writer.writeStartElement("h1");
|
||
writer.writeEmptyElement("img");
|
||
writer.writeAttribute("src", "/debug/logo.svg");
|
||
writer.writeAttribute("class", "nymea-main-logo");
|
||
|
||
//: The main title of the debug server interface
|
||
writer.writeCharacters(tr("nymea debug interface"));
|
||
writer.writeEndElement(); // h1
|
||
writer.writeEndElement(); // div header
|
||
|
||
// Tab bar
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "tab");
|
||
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "tablinks");
|
||
writer.writeAttribute("id", "informationTabButton");
|
||
writer.writeAttribute("onclick", "selectSection(event, 'information-section')");
|
||
//: The name of the section tab in the debug server interface
|
||
writer.writeCharacters(tr("Information"));
|
||
writer.writeEndElement(); // button
|
||
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "tablinks");
|
||
writer.writeAttribute("id", "downloadsTabButton");
|
||
writer.writeAttribute("onclick", "selectSection(event, 'downloads-section')");
|
||
//: The name of the section tab in the debug server interface
|
||
writer.writeCharacters(tr("Downloads"));
|
||
writer.writeEndElement(); // button
|
||
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "tablinks");
|
||
writer.writeAttribute("id", "networkTabButton");
|
||
writer.writeAttribute("onclick", "selectSection(event, 'network-section')");
|
||
//: The name of the section tab in the debug server interface
|
||
writer.writeCharacters(tr("Network"));
|
||
writer.writeEndElement(); // button
|
||
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "tablinks");
|
||
writer.writeAttribute("id", "logsTabButton");
|
||
writer.writeAttribute("onclick", "selectSection(event, 'logs-section')");
|
||
//: The name of the section tab in the debug server interface
|
||
writer.writeCharacters(tr("Logs"));
|
||
writer.writeEndElement(); // button
|
||
|
||
writer.writeEndElement(); // tab
|
||
|
||
// Body
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "body");
|
||
|
||
// ---------------------------------------------------------------------------
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "tabcontent");
|
||
writer.writeAttribute("id", "information-section");
|
||
|
||
// Warning
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning");
|
||
// Warning image
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning-image-area");
|
||
writer.writeEmptyElement("img");
|
||
writer.writeAttribute("class", "warning-image");
|
||
writer.writeAttribute("src", "/debug/warning.svg");
|
||
writer.writeEndElement(); // div warning image
|
||
// Warning message
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning-message");
|
||
//: The warning message of the debug interface
|
||
writer.writeCharacters(tr("Please note that this debug interface may allow accessing sensitive data about the nymea system and connected devices and services. It is recommended to disable it again when not needed any more."));
|
||
writer.writeEndElement(); // div warning message
|
||
writer.writeEndElement(); // div warning
|
||
|
||
|
||
// Server information section
|
||
writer.writeEmptyElement("hr");
|
||
//: The server information section of the debug interface
|
||
writer.writeTextElement("h2", tr("Server information"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeStartElement("table");
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The server name description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Server name"));
|
||
writer.writeTextElement("td", NymeaCore::instance()->configuration()->serverName());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The server version description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Server version"));
|
||
writer.writeTextElement("td", NYMEA_VERSION_STRING);
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The API version description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("JSON-RPC version"));
|
||
writer.writeTextElement("td", JSON_PROTOCOL_VERSION);
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The language description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Language"));
|
||
writer.writeTextElement("td", NymeaCore::instance()->configuration()->locale().name() + " (" + NymeaCore::instance()->configuration()->locale().nativeCountryName() + " - " + NymeaCore::instance()->configuration()->locale().nativeLanguageName() + ")");
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The timezone description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Timezone"));
|
||
writer.writeTextElement("td", QString::fromUtf8(NymeaCore::instance()->configuration()->timeZone()));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The server id description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Server UUID"));
|
||
writer.writeTextElement("td", NymeaCore::instance()->configuration()->serverUuid().toString());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The settings path description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Settings path"));
|
||
writer.writeTextElement("td", NymeaSettings::settingsPath());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The translation path description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Translations path"));
|
||
writer.writeTextElement("td", NymeaSettings(NymeaSettings::SettingsRoleGlobal).translationsPath());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The user name in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("User"));
|
||
writer.writeTextElement("td", qgetenv("USER"));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Command"));
|
||
writer.writeTextElement("td", QCoreApplication::arguments().join(' '));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The Qt build version description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Compiled with Qt version"));
|
||
writer.writeTextElement("td", QT_VERSION_STR);
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The Qt runtime version description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Qt runtime version"));
|
||
writer.writeTextElement("td", qVersion());
|
||
writer.writeEndElement(); // tr
|
||
|
||
if (!qgetenv("SNAP").isEmpty()) {
|
||
// Note: http://snapcraft.io/docs/reference/env
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The snap name description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Snap name"));
|
||
writer.writeTextElement("td", qgetenv("SNAP_NAME"));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The snap version description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Snap version"));
|
||
writer.writeTextElement("td", qgetenv("SNAP_VERSION"));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The snap directory description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Snap directory"));
|
||
writer.writeTextElement("td", qgetenv("SNAP"));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The snap application data description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Snap application data"));
|
||
writer.writeTextElement("td", qgetenv("SNAP_DATA"));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The snap user data description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Snap user data"));
|
||
writer.writeTextElement("td", qgetenv("SNAP_USER_DATA"));
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The snap common data description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Snap common data"));
|
||
writer.writeTextElement("td", qgetenv("SNAP_COMMON"));
|
||
writer.writeEndElement(); // tr
|
||
}
|
||
|
||
writer.writeEndElement(); // table
|
||
|
||
|
||
// System information section
|
||
writer.writeEmptyElement("hr");
|
||
//: The system information section of the debug interface
|
||
writer.writeTextElement("h2", tr("System information"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeStartElement("table");
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Hostname"));
|
||
#if (QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
|
||
writer.writeTextElement("td", QHostInfo::localHostName());
|
||
#else
|
||
writer.writeTextElement("td", QSysInfo::machineHostName());
|
||
#endif
|
||
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Architecture"));
|
||
writer.writeTextElement("td", QSysInfo::currentCpuArchitecture());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Kernel type"));
|
||
writer.writeTextElement("td", QSysInfo::kernelType());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Kernel version"));
|
||
writer.writeTextElement("td", QSysInfo::kernelVersion());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Product type"));
|
||
writer.writeTextElement("td", QSysInfo::productType());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeStartElement("tr");
|
||
//: The command description in the server infromation section of the debug interface
|
||
writer.writeTextElement("th", tr("Product version"));
|
||
writer.writeTextElement("td", QSysInfo::productVersion());
|
||
writer.writeEndElement(); // tr
|
||
|
||
writer.writeEndElement(); // table
|
||
|
||
// Generate report
|
||
writer.writeEmptyElement("hr");
|
||
//: In the server information section of the debug interface
|
||
writer.writeTextElement("h2", tr("Generate report"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeTextElement("p", tr("If you want to provide all the debug information to a developer, you can generate a report file, "
|
||
"which contains all information needed for reproducing a system and get information about possible problems."));
|
||
|
||
// Warning
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning");
|
||
// Warning image
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning-image-area");
|
||
writer.writeEmptyElement("img");
|
||
writer.writeAttribute("class", "warning-image");
|
||
writer.writeAttribute("src", "/debug/warning.svg");
|
||
writer.writeEndElement(); // div warning image
|
||
// Warning message
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning-message");
|
||
//: The warning message of the debug interface
|
||
writer.writeCharacters(tr("Please note that the generated debug report may contain sensitive data about the nymea system and connected devices and services."));
|
||
writer.writeEndElement(); // div warning message
|
||
writer.writeEndElement(); // div warning
|
||
|
||
// Generate report button
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
writer.writeAttribute("id", "generateReportButton");
|
||
writer.writeAttribute("onClick", "generateReport()");
|
||
//: The generate debug report button text of the debug interface
|
||
writer.writeCharacters(tr("Generate report file"));
|
||
writer.writeEndElement(); // button
|
||
|
||
// Logs output
|
||
writer.writeStartElement("textarea");
|
||
writer.writeAttribute("class", "console-textarea");
|
||
writer.writeAttribute("id", "generateReportTextArea");
|
||
writer.writeAttribute("readonly", "readonly");
|
||
writer.writeAttribute("rows", "5");
|
||
writer.writeCharacters("");
|
||
writer.writeEndElement(); // textarea
|
||
|
||
writer.writeEndElement(); // information-section
|
||
|
||
// ---------------------------------------------------------------------------
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "tabcontent");
|
||
writer.writeAttribute("id", "downloads-section");
|
||
|
||
// Downloads section
|
||
writer.writeEmptyElement("hr");
|
||
//: The downloads section of the debug interface
|
||
writer.writeTextElement("h2", tr("Downloads"));
|
||
|
||
// Logs download section
|
||
writer.writeEmptyElement("hr");
|
||
//: The download logs section of the debug interface
|
||
writer.writeTextElement("h3", tr("Logs"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
|
||
// Download row logdb
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The log databse download description of the debug interface
|
||
writer.writeTextElement("p", tr("Log database"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
if (QFileInfo(NymeaCore::instance()->configuration()->logDBName()).exists()) {
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaCore::instance()->configuration()->logDBName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
}
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-button");
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
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
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div download-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
// Download row syslog
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The syslog download description of the debug interface
|
||
writer.writeTextElement("p", tr("System logs"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", "/var/log/syslog");
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-button");
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
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", "showFile('/debug/syslog')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
// Settings download section global
|
||
writer.writeEmptyElement("hr");
|
||
//: The settings download section title of the debug interface
|
||
writer.writeTextElement("h3", tr("Settings"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
// Download row
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The nymead settings download description of the debug interface
|
||
writer.writeTextElement("p", tr("nymead settings"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-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", "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", "showFile('/debug/settings/nymead')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
// Download row things
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The thing settings download description of the debug interface
|
||
writer.writeTextElement("p", tr("Thing settings"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleThings).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-button");
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleThings).fileName())) {
|
||
writer.writeAttribute("disabled", "disabled");
|
||
}
|
||
writer.writeAttribute("onClick", "downloadFile('/debug/settings/things', 'things.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::SettingsRoleThings).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "showFile('/debug/settings/things')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
// Download row rules
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The rules settings download description of the debug interface
|
||
writer.writeTextElement("p", tr("Rules settings"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleRules).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-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", "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", "showFile('/debug/settings/rules')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
// Download row plugins
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The plugins settings download description of the debug interface
|
||
writer.writeTextElement("p", tr("Plugins settings"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRolePlugins).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-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", "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", "showFile('/debug/settings/plugins')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
|
||
// Download row tags
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The tag settings download description of the debug interface
|
||
writer.writeTextElement("p", tr("Tag settings"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleTags).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-button");
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleTags).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "downloadFile('/debug/settings/tags', 'tags.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::SettingsRoleTags).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "showFile('/debug/settings/tags')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
// Download row MQTT policies
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The MQTT policies download description of the debug interface
|
||
writer.writeTextElement("p", tr("MQTT policies"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleMqttPolicies).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-button");
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleMqttPolicies).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "downloadFile('/debug/settings/mqttpolicies', 'mqttpolicies.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::SettingsRoleMqttPolicies).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "showFile('/debug/settings/mqttpolicies')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
|
||
// Download row IO connections
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-row");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-name-column");
|
||
//: The MQTT policies download description of the debug interface
|
||
writer.writeTextElement("p", tr("IO Connections"));
|
||
writer.writeEndElement(); // div download-name-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-path-column");
|
||
writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleIOConnections).fileName());
|
||
writer.writeEndElement(); // div download-path-column
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "download-button-column");
|
||
writer.writeStartElement("form");
|
||
writer.writeAttribute("class", "download-button");
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleMqttPolicies).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "downloadFile('/debug/settings/ioconnections', 'ioconnections.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::SettingsRoleMqttPolicies).fileName())) {
|
||
writer.writeAttribute("disabled", "true");
|
||
}
|
||
writer.writeAttribute("onClick", "showFile('/debug/settings/ioconnections')");
|
||
writer.writeCharacters(tr("Show"));
|
||
writer.writeEndElement(); // button
|
||
writer.writeEndElement(); // form
|
||
writer.writeEndElement(); // div show-button-column
|
||
|
||
writer.writeEndElement(); // div download-row
|
||
|
||
writer.writeEndElement(); // downloads-section
|
||
|
||
|
||
// ---------------------------------------------------------------------------
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "tabcontent");
|
||
writer.writeAttribute("id", "network-section");
|
||
|
||
// 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 where nymea is running has full network connectivity."));
|
||
|
||
// Ping section
|
||
writer.writeEmptyElement("hr");
|
||
//: The ping section of the debug interface
|
||
writer.writeTextElement("h3", tr("Ping"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeTextElement("p", tr("This test makes four ping attempts to the nymea.io server."));
|
||
|
||
// 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 DNS lookup section of the debug interface
|
||
writer.writeTextElement("h3", tr("DNS lookup"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeTextElement("p", tr("This test makes a dynamic name server lookup for nymea.io."));
|
||
|
||
|
||
// 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
|
||
|
||
// Trace section
|
||
writer.writeEmptyElement("hr");
|
||
//: The trace section of the debug interface
|
||
writer.writeTextElement("h3", tr("Trace path"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeTextElement("p", tr("This test shows the trace path from the nymea device to the nymea.io server."));
|
||
|
||
// Start tracepath button
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
writer.writeAttribute("id", "tracePathButton");
|
||
writer.writeAttribute("onClick", "startTracePathTest()");
|
||
//: The trace path 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
|
||
|
||
writer.writeEndElement(); // network-section
|
||
|
||
|
||
// ---------------------------------------------------------------------------
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "tabcontent");
|
||
writer.writeAttribute("id", "logs-section");
|
||
|
||
// Logs stream
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "logstream");
|
||
writer.writeEmptyElement("hr");
|
||
//: The network section of the debug interface
|
||
writer.writeTextElement("h2", tr("Server live logs"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeTextElement("p", tr("This section allows you to see the live logs of the nymea server."));
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "log-buttons");
|
||
|
||
// Toggle log button
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
writer.writeAttribute("id", "toggleLogsButton");
|
||
writer.writeAttribute("onClick", "toggleWebsocketConnection()");
|
||
//: The connect button for the log stream of the debug interface
|
||
writer.writeCharacters(tr("Start logs"));
|
||
writer.writeEndElement(); // button
|
||
|
||
// Copy log content button
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
writer.writeAttribute("id", "copyLogsButton");
|
||
writer.writeAttribute("onClick", "copyLogsContent()");
|
||
writer.writeEmptyElement("img");
|
||
writer.writeAttribute("class", "tool-image");
|
||
writer.writeAttribute("src", "/debug/edit-copy.svg");
|
||
writer.writeEndElement(); // button
|
||
|
||
// Copy log content button
|
||
writer.writeStartElement("button");
|
||
writer.writeAttribute("class", "button");
|
||
writer.writeAttribute("type", "button");
|
||
writer.writeAttribute("id", "clearLogsButton");
|
||
writer.writeAttribute("onClick", "clearLogsContent()");
|
||
writer.writeEmptyElement("img");
|
||
writer.writeAttribute("class", "tool-image");
|
||
writer.writeAttribute("src", "/debug/delete.svg");
|
||
writer.writeEndElement(); // button
|
||
|
||
writer.writeEndElement(); // div log-buttons
|
||
|
||
|
||
// Logs 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.writeEmptyElement("hr");
|
||
//: The network section of the debug interface
|
||
writer.writeTextElement("h2", tr("Logging filters"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "categories-area");
|
||
|
||
QStringList loggingCategories = NymeaCore::loggingFilters();
|
||
loggingCategories.sort();
|
||
|
||
QHash<QString, QString> categoryMap = {
|
||
{"debug", "🐞 Debug"},
|
||
{"info", "ℹ️ Info"},
|
||
{"warning", "⚠️ Warning"},
|
||
{"critical", "🔥 Critical"}
|
||
};
|
||
foreach (const QString &loggingCategory, loggingCategories) {
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "debug-category");
|
||
writer.writeTextElement("p", loggingCategory);
|
||
writer.writeStartElement("label");
|
||
writer.writeStartElement("select");
|
||
writer.writeAttribute("class", "debug-select");
|
||
writer.writeAttribute("onchange", QString("toggleLoggingCategory('%1', this)").arg(loggingCategory));
|
||
writer.writeAttribute("id", QString("debug-category-%1").arg(loggingCategory));
|
||
foreach (const QString &option, QStringList({"debug", "info", "warning", "critical"})) {
|
||
writer.writeStartElement("option");
|
||
writer.writeAttribute("value", option);
|
||
writer.writeCharacters(categoryMap.value(option));
|
||
writer.writeEndElement();
|
||
}
|
||
writer.writeEndElement(); // select
|
||
writer.writeEndElement(); // label
|
||
writer.writeEndElement(); // div debug-category
|
||
}
|
||
|
||
writer.writeEndElement(); // div categories-area
|
||
|
||
writer.writeEmptyElement("hr");
|
||
//: The network section of the debug interface
|
||
writer.writeTextElement("h2", tr("Logging filters plugins"));
|
||
writer.writeEmptyElement("hr");
|
||
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "categories-area");
|
||
|
||
QStringList loggingCategoriesPlugins = NymeaCore::loggingFiltersPlugins();
|
||
loggingCategoriesPlugins.sort();
|
||
foreach (const QString &loggingCategory, loggingCategoriesPlugins) {
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "debug-category");
|
||
writer.writeTextElement("p", loggingCategory);
|
||
writer.writeStartElement("label");
|
||
writer.writeStartElement("select");
|
||
writer.writeAttribute("class", "debug-select");
|
||
writer.writeAttribute("onchange", QString("toggleLoggingCategory('%1', this)").arg(loggingCategory));
|
||
writer.writeAttribute("id", QString("debug-category-%1").arg(loggingCategory));
|
||
foreach (const QString &option, QStringList({"debug", "info", "warning", "critical"})) {
|
||
writer.writeStartElement("option");
|
||
writer.writeAttribute("value", option);
|
||
writer.writeCharacters(categoryMap.value(option));
|
||
writer.writeEndElement();
|
||
}
|
||
writer.writeEndElement(); // select
|
||
writer.writeEndElement(); // label
|
||
writer.writeEndElement(); // div debug-category
|
||
}
|
||
|
||
writer.writeEndElement(); // div categories-area
|
||
|
||
writer.writeEndElement(); // logs-section
|
||
|
||
writer.writeEndElement(); // div body
|
||
|
||
// Footer
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "footer");
|
||
writer.writeTextElement("p", QString("Copyright %1 %2 nymea GmbH.").arg(QChar(0xA9)).arg(COPYRIGHT_YEAR_STRING));
|
||
//: The footer license note of the debug interface
|
||
writer.writeTextElement("p", tr("Released under the GNU GENERAL PUBLIC LICENSE Version 3."));
|
||
writer.writeEndElement(); // div footer
|
||
|
||
writer.writeEndElement(); // div container
|
||
|
||
writer.writeEndElement(); // html
|
||
|
||
return data;
|
||
}
|
||
|
||
QByteArray DebugServerHandler::createErrorXmlDocument(HttpReply::HttpStatusCode statusCode, const QString &errorMessage)
|
||
{
|
||
QByteArray data;
|
||
QXmlStreamWriter writer(&data);
|
||
writer.setAutoFormatting(true);
|
||
writer.writeStartDocument("1.0");
|
||
writer.writeComment("Live generated html page from nymea");
|
||
writer.writeStartElement("html");
|
||
writer.writeAttribute("lang", NymeaCore::instance()->configuration()->locale().name());
|
||
|
||
// Head
|
||
writer.writeStartElement("head");
|
||
|
||
writer.writeEmptyElement("meta");
|
||
writer.writeAttribute("http-equiv", "Content-Type");
|
||
writer.writeAttribute("content", "text/html; charset=utf-8");
|
||
|
||
writer.writeEmptyElement("link");
|
||
writer.writeAttribute("rel", "stylesheet");
|
||
writer.writeAttribute("href", "/debug/styles.css");
|
||
|
||
writer.writeTextElement("title", tr("Debug nymea"));
|
||
|
||
writer.writeEndElement(); // head
|
||
|
||
// Container
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "container");
|
||
|
||
// Header
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "header");
|
||
writer.writeTextElement("p", " ");
|
||
//: The HTTP error message of the debug interface. The %1 represents the error code ie.e 404
|
||
writer.writeTextElement("h1", tr("Error %1").arg(static_cast<int>(statusCode)));
|
||
writer.writeEndElement(); // div header
|
||
|
||
// Body
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "body");
|
||
|
||
// Warning
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning");
|
||
// Warning image
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning-image-area");
|
||
writer.writeEmptyElement("img");
|
||
writer.writeAttribute("class", "warning-image");
|
||
writer.writeAttribute("src", "/debug/warning.svg");
|
||
writer.writeEndElement(); // div warning image
|
||
// Warning message
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "warning-message");
|
||
writer.writeCharacters(errorMessage);
|
||
writer.writeEndElement(); // div warning message
|
||
writer.writeEndElement(); // div warning
|
||
|
||
writer.writeEndElement(); // div body
|
||
|
||
// Footer
|
||
writer.writeStartElement("div");
|
||
writer.writeAttribute("class", "footer");
|
||
writer.writeTextElement("p", QString("Copyright %1 %2 nymea GmbH.").arg(QChar(0xA9)).arg(COPYRIGHT_YEAR_STRING));
|
||
writer.writeTextElement("p", tr("Released under the GNU GENERAL PUBLIC LICENSE Version 2."));
|
||
writer.writeEndElement(); // div footer
|
||
|
||
writer.writeEndElement(); // div container
|
||
writer.writeEndElement(); // html
|
||
|
||
return data;
|
||
}
|
||
|
||
}
|