/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2018 Simon Stürz * * * * This file is part of nymea. * * * * nymea is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, version 2 of the License. * * * * nymea is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "nymeacore.h" #include "httpreply.h" #include "nymeasettings.h" #include "httprequest.h" #include "loggingcategories.h" #include "debugserverhandler.h" #include "stdio.h" #include #include #include #include QtMessageHandler DebugServerHandler::s_oldLogMessageHandler = nullptr; QList DebugServerHandler::s_websocketClients; namespace nymeaserver { 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 = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createDebugXmlDocument()); return reply; } // Check if this is a logdb requested if (requestPath.startsWith("/debug/logdb.sql")) { qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); //: The HTTP error message of the debug interface. The %1 represents the file name. reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(logDatabaseFile.fileName()))); return reply; } if (!logDatabaseFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read log database file for debug download" << NymeaCore::instance()->configuration()->logDBName(); HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); //: The HTTP error message of the debug interface. The %1 represents the file name. reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(logDatabaseFile.fileName()))); return reply; } QByteArray logDatabaseRawData = logDatabaseFile.readAll(); logDatabaseFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "application/sql"); reply->setPayload(logDatabaseRawData); return reply; } // Check if this is a syslog requested if (requestPath.startsWith("/debug/syslog")) { QString syslogFileName = "/var/log/syslog"; qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(syslogFileName))); return reply; } if (!syslogFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read syslog file for debug download" << syslogFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(syslogFileName))); return reply; } QByteArray syslogFileData = syslogFile.readAll(); syslogFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(syslogFileData); return reply; } // Check if this is a settings request if (requestPath.startsWith("/debug/settings")) { if (requestPath.startsWith("/debug/settings/devices")) { QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleDevices).fileName(); qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/rules")) { QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleRules).fileName(); qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/nymead")) { QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleGlobal).fileName(); qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/devicestates")) { QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).fileName(); qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/plugins")) { QString settingsFileName = NymeaSettings(NymeaSettings::SettingsRolePlugins).fileName(); qCDebug(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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } if (requestPath.startsWith("/debug/settings/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 = RestResource::createErrorReply(HttpReply::NotFound); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not find file \"%1\".").arg(settingsFileName))); return reply; } if (!settingsFile.open(QFile::ReadOnly)) { qCWarning(dcDebugServer()) << "Could not read file for debug download" << settingsFileName; HttpReply *reply = RestResource::createErrorReply(HttpReply::Forbidden); reply->setHeader(HttpReply::ContentTypeHeader, "text/html"); reply->setPayload(createErrorXmlDocument(HttpReply::NotFound, tr("Could not open file \"%1\".").arg(settingsFileName))); return reply; } QByteArray settingsFileData = settingsFile.readAll(); settingsFile.close(); HttpReply *reply = RestResource::createSuccessReply(); reply->setHeader(HttpReply::ContentTypeHeader, "text/plain"); reply->setPayload(settingsFileData); return reply; } } if (requestPath.startsWith("/debug/ping")) { // Only one ping process should run if (m_pingProcess || m_pingReply) return RestResource::createErrorReply(HttpReply::InternalServerError); qCDebug(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 = RestResource::createAsyncReply(); return m_pingReply; } if (requestPath.startsWith("/debug/dig")) { // Only one dig process should run if (m_digProcess || m_digReply) return RestResource::createErrorReply(HttpReply::InternalServerError); qCDebug(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 = RestResource::createAsyncReply(); return m_digReply; } if (requestPath.startsWith("/debug/tracepath")) { // Only one tracepath process should run if (m_tracePathProcess || m_tracePathReply) return RestResource::createErrorReply(HttpReply::InternalServerError); qCDebug(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 = RestResource::createAsyncReply(); return m_tracePathReply; } if (requestPath.startsWith("/debug/report")) { // 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_finishedReportGenerators.contains(fileName)) { HttpReply *downloadReportReply = RestResource::createSuccessReply(); DebugReportGenerator *generator = m_finishedReportGenerators.take(fileName); downloadReportReply->setPayload(generator->reportFileData()); downloadReportReply->setHeader(HttpReply::ContentTypeHeader, "application/tar+gzip;"); generator->deleteLater(); return downloadReportReply; } else { qCWarning(dcDebugServer()) << "The requested file does not exist any more" << fileName; HttpReply *downloadReportReply = RestResource::createErrorReply(HttpReply::NotFound); return downloadReportReply; } } else { DebugReportGenerator *debugReportGenerator = new DebugReportGenerator(this); connect(debugReportGenerator, &DebugReportGenerator::finished, this, &DebugServerHandler::onDebugReportGeneratorFinished); connect(debugReportGenerator, &DebugReportGenerator::timeout, this, &DebugServerHandler::onDebugReportGeneratorTimeout); debugReportGenerator->generateReport(); HttpReply *debugReportReply = RestResource::createAsyncReply(); m_runningReportGenerators.insert(debugReportGenerator, debugReportReply); return debugReportReply; } } // 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 = RestResource::createErrorReply(HttpReply::PermanentRedirect); reply->setHeader(HttpReply::LocationHeader, "/debug"); return reply; } void DebugServerHandler::logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) { s_oldLogMessageHandler(type, context, 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; } 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 = RestResource::createSuccessReply(); reply->setPayload(data); // Check content type if (resourceFileName.endsWith(".css")) { reply->setHeader(HttpReply::ContentTypeHeader, "text/css; charset=\"utf-8\";"); } else if (resourceFileName.endsWith(".svg")) { reply->setHeader(HttpReply::ContentTypeHeader, "image/svg+xml; charset=\"utf-8\";"); } else if (resourceFileName.endsWith(".js")) { reply->setHeader(HttpReply::ContentTypeHeader, "text/javascript; charset=\"utf-8\";"); } else if (resourceFileName.endsWith(".png")) { reply->setHeader(HttpReply::ContentTypeHeader, "image/png"); } return reply; } 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."; s_oldLogMessageHandler = qInstallMessageHandler(&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(sender()); qCDebug(dcDebugServer()) << "Websocket client disconnected" << client->peerAddress().toString(); s_websocketClients.removeAll(client); client->deleteLater(); if (s_websocketClients.isEmpty()) { qCDebug(dcDebugServer()) << "Uninstall debug message handler for live logs and restore default message handler"; qInstallMessageHandler(s_oldLogMessageHandler); s_oldLogMessageHandler = nullptr; } } void DebugServerHandler::onWebsocketClientError(QAbstractSocket::SocketError error) { QWebSocket *client = static_cast(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); 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); 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); 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(sender()); qCDebug(dcDebugServer()) << "Report generation finished" << (success ? "successfully" : "with error") << debugReportGenerator->reportFileName(); HttpReply *httpReply = m_runningReportGenerators.take(debugReportGenerator); if (success) { QVariantMap reportInformation; reportInformation.insert("fileName", debugReportGenerator->reportFileName()); reportInformation.insert("fileSize", debugReportGenerator->reportFileData().size()); reportInformation.insert("md5sum", debugReportGenerator->md5Sum()); httpReply->setHttpStatusCode(HttpReply::Ok); httpReply->setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";"); httpReply->setPayload(QJsonDocument::fromVariant(reportInformation).toJson(QJsonDocument::Indented)); m_finishedReportGenerators.insert(debugReportGenerator->reportFileName(), debugReportGenerator); } else { httpReply->setHttpStatusCode(HttpReply::InternalServerError); } httpReply->finished(); } void DebugServerHandler::onDebugReportGeneratorTimeout() { DebugReportGenerator *debugReportGenerator = static_cast(sender()); qCWarning(dcDebugServer()) << "Report generation timeouted. Cleaning up" << debugReportGenerator->reportFileName(); if (m_finishedReportGenerators.values().contains(debugReportGenerator)) { m_finishedReportGenerators.remove(debugReportGenerator->reportFileName()); debugReportGenerator->deleteLater(); } } 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 // 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"); 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"); //: 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"); //: The welcome message of the debug interface writer.writeTextElement("p", tr("Welcome to the debug interface.")); writer.writeTextElement("p", tr("This debug interface was designed to provide an easy possibility to get helpful information about the running nymea server.")); // 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("Be aware that this debug interface is a security risk and could offer access to sensible data.")); writer.writeEndElement(); // div warning message writer.writeEndElement(); // div warning // System 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 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 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 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 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.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 for (int i = 0; i < NymeaCore::instance()->deviceManager()->pluginSearchDirs().count(); i++) { writer.writeStartElement("tr"); writer.writeEndElement(); // tr if (i == 0) { //: The plugins path description in the server infromation section of the debug interface writer.writeTextElement("th", tr("Plugin paths")); } else { writer.writeTextElement("th", ""); } writer.writeTextElement("td", QFileInfo(NymeaCore::instance()->deviceManager()->pluginSearchDirs().at(i)).absoluteFilePath()); } 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("Do not share these generated information public, since they can contain sensible data and should be shared very carefully and only with people you trust!")); 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 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 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 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 writer.writeStartElement("div"); writer.writeAttribute("class", "download-row"); writer.writeStartElement("div"); writer.writeAttribute("class", "download-name-column"); //: The device settings download description of the debug interface writer.writeTextElement("p", tr("Device settings")); writer.writeEndElement(); // div download-name-column writer.writeStartElement("div"); writer.writeAttribute("class", "download-path-column"); writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleDevices).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::SettingsRoleDevices).fileName())) { writer.writeAttribute("disabled", "disabled"); } writer.writeAttribute("onClick", "downloadFile('/debug/settings/devices', 'devices.conf')"); writer.writeCharacters(tr("Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // div download-button-column writer.writeStartElement("div"); writer.writeAttribute("class", "show-button-column"); writer.writeStartElement("form"); writer.writeAttribute("class", "show-button"); writer.writeStartElement("button"); writer.writeAttribute("class", "button"); writer.writeAttribute("type", "button"); if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleDevices).fileName())) { writer.writeAttribute("disabled", "true"); } writer.writeAttribute("onClick", "showFile('/debug/settings/devices')"); writer.writeCharacters(tr("Show")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // div show-button-column writer.writeEndElement(); // div download-row // Download row writer.writeStartElement("div"); writer.writeAttribute("class", "download-row"); writer.writeStartElement("div"); writer.writeAttribute("class", "download-name-column"); //: The device states settings download description of the debug interface writer.writeTextElement("p", tr("Device states settings")); writer.writeEndElement(); // div download-name-column writer.writeStartElement("div"); writer.writeAttribute("class", "download-path-column"); writer.writeTextElement("p", NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).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::SettingsRoleDeviceStates).fileName())) { writer.writeAttribute("disabled", "true"); } writer.writeAttribute("onClick", "downloadFile('/debug/settings/devicestates', 'devicestates.conf')"); writer.writeCharacters(tr("Download")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // div download-button-column writer.writeStartElement("div"); writer.writeAttribute("class", "show-button-column"); writer.writeStartElement("form"); writer.writeAttribute("class", "show-button"); writer.writeStartElement("button"); writer.writeAttribute("class", "button"); writer.writeAttribute("type", "button"); if (!QFile::exists(NymeaSettings(NymeaSettings::SettingsRoleDeviceStates).fileName())) { writer.writeAttribute("disabled", "true"); } writer.writeAttribute("onClick", "showFile('/debug/settings/devicestates')"); writer.writeCharacters(tr("Show")); writer.writeEndElement(); // button writer.writeEndElement(); // form writer.writeEndElement(); // div show-button-column writer.writeEndElement(); // div download-row // Download row 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 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 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::SettingsRoleDeviceStates).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 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 showes 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 allowes you to see the live logs of the nymea server.")); // 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 // 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.writeEndElement(); // logs-section writer.writeEndElement(); // div body // Footer writer.writeStartElement("div"); writer.writeAttribute("class", "footer"); writer.writeTextElement("p", QString("Copyright %1 2018 guh GmbH.").arg(QChar(0xA9))); //: The footer license note of the debug interface 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; } 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(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 2018 guh GmbH.").arg(QChar(0xA9))); 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; } }