embedded webui + config modification detection (#240)

* implement embedded webui

* add detection for changed config, later on used for restart hyperion
This commit is contained in:
redPanther 2016-09-14 13:51:28 +02:00 committed by GitHub
parent ccc50899fb
commit 1cc2f72fa2
16 changed files with 103 additions and 30 deletions

View File

@ -14,7 +14,7 @@ table.borderless td,table.borderless th{border: none !important;}
/*Header*/ /*Header*/
.navbar-brand{padding: 5px;padding-left:20px;height:60px;} .navbar-brand{padding: 5px;padding-left:20px;height:60px;}
.sidebar{margin-top:91px;} .sidebar{margin-top:61px;padding-top:20px;}
.dropdown{font-size:18px;} .dropdown{font-size:18px;}
@media (max-width: 767px) {.sidebar{margin-top:0px;}} @media (max-width: 767px) {.sidebar{margin-top:0px;}}

View File

@ -80,7 +80,6 @@
<body> <body>
<div id="loading_overlay"></div> <div id="loading_overlay"></div>
<div id="wrapper"> <div id="wrapper">
<!-- Navigation --> <!-- Navigation -->
@ -225,7 +224,16 @@
</nav> </nav>
<!-- Page Content --> <!-- Page Content -->
<div id="page-wrapper" /> <div id="page-wrapper" style="padding-top:10px">
<div id="hyperion_restart_notify" class="alert alert-warning" style="display:none;padding:10px;margin:0">
<div class="panel-danger" style="text-align:right">
<div style="float:left">Hyperion Configuration is modified. To make it active, restart Hyperion.</div>
<button id="btn_hyperion_restart" class="btn btn-danger">Restart Hyperion</button>
</div>
</div>
<div id="page-content" />
</div>
</div> </div>
<!-- /#wrapper --> <!-- /#wrapper -->

View File

@ -20,6 +20,12 @@ $(document).ready( function() {
parsedServerInfoJSON = event.response; parsedServerInfoJSON = event.response;
currentVersion = parsedServerInfoJSON.info.hyperion[0].version; currentVersion = parsedServerInfoJSON.info.hyperion[0].version;
cleanCurrentVersion = currentVersion.replace(/\./g, ''); cleanCurrentVersion = currentVersion.replace(/\./g, '');
if (parsedServerInfoJSON.info.hyperion[0].config_modified)
$("#hyperion_restart_notify").fadeIn("fast");
else
$("#hyperion_restart_notify").fadeOut("fast");
// get active led device // get active led device
var leddevice = parsedServerInfoJSON.info.ledDevices.active; var leddevice = parsedServerInfoJSON.info.ledDevices.active;
$('#dash_leddevice').html(leddevice); $('#dash_leddevice').html(leddevice);

View File

@ -149,7 +149,7 @@ $(document).ready(function() {
isCurrentDevice = (server.info.ledDevices.active == $(this).val()); isCurrentDevice = (server.info.ledDevices.active == $(this).val());
for(var key in parsedConfJSON.device){ for(var key in parsedConfJSON.device){
if (key in generalOptions.properties) if (key != "type" && key in generalOptions.properties)
values_general[key] = parsedConfJSON.device[key]; values_general[key] = parsedConfJSON.device[key];
}; };
grabber_conf_editor.getEditor("root.generalOptions").setValue( values_general ); grabber_conf_editor.getEditor("root.generalOptions").setValue( values_general );

View File

@ -1,13 +1,13 @@
function bindNavToContent(containerId, fileName, loadNow) function bindNavToContent(containerId, fileName, loadNow)
{ {
$("#page-wrapper").off(); $("#page-content").off();
$(containerId).on("click", function() { $(containerId).on("click", function() {
$("#page-wrapper").load("/content/"+fileName+".html"); $("#page-content").load("/content/"+fileName+".html");
}); });
if (loadNow) if (loadNow)
{ {
$("#page-wrapper").load("/content/"+fileName+".html"); $("#page-content").load("/content/"+fileName+".html");
} }
} }

View File

@ -195,8 +195,6 @@
"webConfig" : "webConfig" :
{ {
"enable" : true, "enable" : true,
"document_root" : "/usr/share/hyperion/webconfig",
"port" : 8099
}, },
"effects" : "effects" :

View File

@ -163,6 +163,8 @@ public:
ComponentRegister& getComponentRegister() { return _componentRegister; }; ComponentRegister& getComponentRegister() { return _componentRegister; };
bool configModified();
public slots: public slots:
/// ///
/// Writes a single color to all the leds for the given time and priority /// Writes a single color to all the leds for the given time and priority
@ -386,5 +388,6 @@ private:
/// holds the current priority channel that is manualy selected /// holds the current priority channel that is manualy selected
int _currentSourcePriority; int _currentSourcePriority;
QByteArray _configHash;
}; };

View File

@ -26,7 +26,7 @@ private:
quint16 _port; quint16 _port;
StaticFileServing* _server; StaticFileServing* _server;
const std::string WEBCONFIG_DEFAULT_PATH = "/usr/share/hyperion/webconfig"; const QString WEBCONFIG_DEFAULT_PATH = ":/webconfig";
const quint16 WEBCONFIG_DEFAULT_PORT = 8099; const quint16 WEBCONFIG_DEFAULT_PORT = 8099;
}; };

View File

@ -38,7 +38,7 @@ CONFIGURE_FILE(${CURRENT_SOURCE_DIR}/EffectEngine.qrc.in ${CMAKE_BINARY_DIR}/Eff
SET(EffectEngine_RESOURCES ${CMAKE_BINARY_DIR}/EffectEngine.qrc) SET(EffectEngine_RESOURCES ${CMAKE_BINARY_DIR}/EffectEngine.qrc)
QT5_WRAP_CPP(EffectEngineHEADERS_MOC ${EffectEngineQT_HEADERS}) QT5_WRAP_CPP(EffectEngineHEADERS_MOC ${EffectEngineQT_HEADERS})
qt5_add_resources(EffectEngine_RESOURCES_RCC ${EffectEngine_RESOURCES} OPTIONS "-no-compress") qt5_add_resources(EffectEngine_RESOURCES_RCC ${EffectEngine_RESOURCES} ) # OPTIONS "-no-compress"
add_library(effectengine add_library(effectengine
${EffectEngineHEADERS} ${EffectEngineHEADERS}

View File

@ -10,6 +10,8 @@
#include <QRegExp> #include <QRegExp>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QCryptographicHash>
#include <QFile>
// JsonSchema include // JsonSchema include
#include <utils/jsonschema/JsonFactory.h> #include <utils/jsonschema/JsonFactory.h>
@ -575,6 +577,7 @@ Hyperion::Hyperion(const Json::Value &jsonConfig, const std::string configFile)
, _log(Logger::getInstance("Core")) , _log(Logger::getInstance("Core"))
, _hwLedCount(_ledString.leds().size()) , _hwLedCount(_ledString.leds().size())
, _sourceAutoSelectEnabled(true) , _sourceAutoSelectEnabled(true)
, _configHash()
{ {
registerPriority("Off", PriorityMuxer::LOWEST_PRIORITY); registerPriority("Off", PriorityMuxer::LOWEST_PRIORITY);
@ -623,6 +626,9 @@ Hyperion::Hyperion(const Json::Value &jsonConfig, const std::string configFile)
Debug(_log,"configured leds: %d hw leds: %d", getLedCount(), _hwLedCount); Debug(_log,"configured leds: %d hw leds: %d", getLedCount(), _hwLedCount);
WarningIf(hwLedCount < getLedCount(), _log, "more leds configured than available. check 'ledCount' in 'device' section"); WarningIf(hwLedCount < getLedCount(), _log, "more leds configured than available. check 'ledCount' in 'device' section");
// initialize hash of current config
configModified();
// initialize the leds // initialize the leds
update(); update();
} }
@ -648,6 +654,29 @@ unsigned Hyperion::getLedCount() const
return _ledString.leds().size(); return _ledString.leds().size();
} }
bool Hyperion::configModified()
{
QFile f(_configFile.c_str());
if (f.open(QFile::ReadOnly))
{
QCryptographicHash hash(QCryptographicHash::Sha1);
if (hash.addData(&f))
{
if (_configHash.size() == 0)
{
_configHash = hash.result();
qDebug(_configHash.toHex());
return false;
}
return _configHash != hash.result();
}
}
f.close();
return false;
}
void Hyperion::registerPriority(const std::string name, const int priority) void Hyperion::registerPriority(const std::string name, const int priority)
{ {
Info(_log, "Register new input source named '%s' for priority channel '%d'", name.c_str(), priority ); Info(_log, "Register new input source named '%s' for priority channel '%d'", name.c_str(), priority );

View File

@ -1000,7 +1000,6 @@
{ {
"type" : "object", "type" : "object",
"title" : "WebUI - DANGER CHANGES CAN MAKE THE WEBUI UNREACHABLE!", "title" : "WebUI - DANGER CHANGES CAN MAKE THE WEBUI UNREACHABLE!",
"required" : true,
"properties" : "properties" :
{ {
"enable" : "enable" :
@ -1009,21 +1008,18 @@
"format": "checkbox", "format": "checkbox",
"title" : "Activate", "title" : "Activate",
"default" : true, "default" : true,
"required" : true,
"propertyOrder" : 1 "propertyOrder" : 1
}, },
"document_root" : "document_root" :
{ {
"type" : "string", "type" : "string",
"title" : "Document Root", "title" : "Document Root"
"required" : true
}, },
"port" : "port" :
{ {
"type" : "integer", "type" : "integer",
"title" : "Port", "title" : "Port",
"default" : 8099, "default" : 8099
"required" : true
} }
}, },
"additionalProperties" : false "additionalProperties" : false

View File

@ -669,9 +669,9 @@ void JsonClientConnection::handleServerInfoCommand(const Json::Value &, const st
ver["version"] = HYPERION_VERSION; ver["version"] = HYPERION_VERSION;
ver["build"] = HYPERION_BUILD_ID; ver["build"] = HYPERION_BUILD_ID;
ver["time"] = __DATE__ " " __TIME__; ver["time"] = __DATE__ " " __TIME__;
ver["config_modified"] = _hyperion->configModified();
info["hyperion"].append(ver); info["hyperion"].append(ver);
// send the result // send the result
sendMessage(result); sendMessage(result);
} }

View File

@ -27,14 +27,23 @@ set(WebConfig_SOURCES
${CURRENT_SOURCE_DIR}/StaticFileServing.cpp ${CURRENT_SOURCE_DIR}/StaticFileServing.cpp
${CURRENT_SOURCE_DIR}/WebConfig.cpp ${CURRENT_SOURCE_DIR}/WebConfig.cpp
) )
FILE ( GLOB_RECURSE webFiles RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig/* )
FOREACH( f ${webFiles} )
STRING ( REPLACE "../assets/webconfig/" "" fname ${f})
SET(HYPERION_WEBCONFIG_RES "${HYPERION_WEBCONFIG_RES}\n\t\t<file alias=\"/webconfig/${fname}\">${f}</file>")
ENDFOREACH()
CONFIGURE_FILE(${CURRENT_SOURCE_DIR}/WebConfig.qrc.in ${CMAKE_BINARY_DIR}/WebConfig.qrc )
SET(WebConfig_RESOURCES ${CMAKE_BINARY_DIR}/WebConfig.qrc)
qt5_wrap_cpp(WebConfig_HEADERS_MOC ${WebConfig_QT_HEADERS}) qt5_wrap_cpp(WebConfig_HEADERS_MOC ${WebConfig_QT_HEADERS})
qt5_add_resources(WebConfig_RESOURCES_RCC ${WebConfig_RESOURCES} ) #OPTIONS "-no-compress"
add_library(webconfig add_library(webconfig
${WebConfig_HEADERS} ${WebConfig_HEADERS}
${WebConfig_QT_HEADERS} ${WebConfig_QT_HEADERS}
${WebConfig_SOURCES} ${WebConfig_SOURCES}
${WebConfig_HEADERS_MOC} ${WebConfig_HEADERS_MOC}
${WebConfig_RESOURCES_RCC}
) )
qt5_use_modules(webconfig Network) qt5_use_modules(webconfig Network)

View File

@ -7,14 +7,17 @@
#include <QPair> #include <QPair>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QResource>
StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent) StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent)
: QObject (parent) : QObject (parent)
, _hyperion(hyperion) , _hyperion(hyperion)
, _baseUrl (baseUrl) , _baseUrl (baseUrl)
, _cgi(hyperion, baseUrl, this) , _cgi(hyperion, baseUrl, this)
, _log(Logger::getInstance("WEBSERVER")) , _log(Logger::getInstance("WEBSERVER"))
{ {
Q_INIT_RESOURCE(WebConfig);
_mimeDb = new QMimeDatabase; _mimeDb = new QMimeDatabase;
_server = new QtHttpServer (this); _server = new QtHttpServer (this);
@ -26,6 +29,7 @@ StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint
connect (_server, &QtHttpServer::requestNeedsReply, this, &StaticFileServing::onRequestNeedsReply); connect (_server, &QtHttpServer::requestNeedsReply, this, &StaticFileServing::onRequestNeedsReply);
_server->start (port); _server->start (port);
} }
StaticFileServing::~StaticFileServing () StaticFileServing::~StaticFileServing ()
@ -75,6 +79,7 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl
} }
return; return;
} }
Q_INIT_RESOURCE(WebConfig);
QFileInfo info(_baseUrl % "/" % path); QFileInfo info(_baseUrl % "/" % path);
if ( path == "/" || path.isEmpty() || ! info.exists() ) if ( path == "/" || path.isEmpty() || ! info.exists() )

View File

@ -1,17 +1,18 @@
#include "webconfig/WebConfig.h" #include "webconfig/WebConfig.h"
#include "StaticFileServing.h" #include "StaticFileServing.h"
#include <QFileInfo>
WebConfig::WebConfig(QObject * parent) WebConfig::WebConfig(QObject * parent)
: QObject(parent) : QObject(parent)
, _hyperion(Hyperion::getInstance())
, _port(WEBCONFIG_DEFAULT_PORT)
, _server(nullptr) , _server(nullptr)
{ {
_port = WEBCONFIG_DEFAULT_PORT; _baseUrl = WEBCONFIG_DEFAULT_PATH;
_hyperion = Hyperion::getInstance();
const Json::Value &config = _hyperion->getJsonConfig(); const Json::Value &config = _hyperion->getJsonConfig();
_baseUrl = QString::fromStdString(WEBCONFIG_DEFAULT_PATH); Logger* log = Logger::getInstance("WEBSERVER");
_port = WEBCONFIG_DEFAULT_PORT;
bool webconfigEnable = true; bool webconfigEnable = true;
if (config.isMember("webConfig")) if (config.isMember("webConfig"))
@ -19,11 +20,24 @@ WebConfig::WebConfig(QObject * parent)
const Json::Value & webconfigConfig = config["webConfig"]; const Json::Value & webconfigConfig = config["webConfig"];
webconfigEnable = webconfigConfig.get("enable", true).asBool(); webconfigEnable = webconfigConfig.get("enable", true).asBool();
_port = webconfigConfig.get("port", WEBCONFIG_DEFAULT_PORT).asUInt(); _port = webconfigConfig.get("port", WEBCONFIG_DEFAULT_PORT).asUInt();
_baseUrl = QString::fromStdString( webconfigConfig.get("document_root", WEBCONFIG_DEFAULT_PATH).asString() ); _baseUrl = QString::fromStdString( webconfigConfig.get("document_root", _baseUrl.toStdString()).asString() );
} }
if (_baseUrl != ":/webconfig")
{
QFileInfo info(_baseUrl);
if (!info.exists() || !info.isDir())
{
Error(log, "document_root '%s' is invalid, set to default '%s'", _baseUrl.toUtf8().constData(), WEBCONFIG_DEFAULT_PATH.toUtf8().constData());
_baseUrl = WEBCONFIG_DEFAULT_PATH;
}
}
Debug(log, "WebUI initialized, document root: %s", _baseUrl.toUtf8().constData());
if ( webconfigEnable ) if ( webconfigEnable )
{
start(); start();
}
} }

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
${HYPERION_WEBCONFIG_RES}
</qresource>
</RCC>