Compare commits

...

10 Commits

Author SHA1 Message Date
jenkins
5b73038726 Jenkins release build 1.14.2 2026-02-19 16:27:40 +01:00
Simon Stürz
0070d7e3dd Update PlatformUpdateController implementation
Introduce UpdateType for PlatformUpdateController implementations
Add optional update progress
Bump JSON-RPC API version to version 8.5
2026-02-19 14:35:20 +01:00
jenkins
ab61ed9a8b Jenkins release build 1.14.1 2026-02-10 12:05:47 +01:00
jenkins
3c7e1bed4f Merge PR #735: Thing: Add state name based set method for possible values 2026-02-10 12:05:46 +01:00
jenkins
89ad2d0f12 Merge PR #734: Update default loations for the mac-addresses.db to be more generic 2026-02-10 12:05:45 +01:00
jenkins
028da6a3b4 Merge PR #733: JsonRpc Server: Improve token verification handling 2026-02-10 12:05:43 +01:00
Simon Stürz
b58c5646df Thing: Add state name based set method for possible values 2026-02-10 10:11:35 +01:00
Simon Stürz
53b00a950f MacAddressDatabase: Update default loations for the mac-addresses.db to be more generic. 2026-01-29 21:24:36 +01:00
Simon Stürz
5eb5c6628b JsonRpc Server: Improve token verification handling depending on the interface configuration 2026-01-29 12:24:28 +01:00
Simon Stürz
a1d0574e20 LogEngine: Improve behavior of disabled logengine 2026-01-22 11:58:58 +01:00
20 changed files with 458 additions and 205 deletions

View File

@ -1,3 +1,20 @@
nymea (1.14.2) noble; urgency=medium
[ Simon Stürz ]
* Improve system update plugin API
-- jenkins <developer@nymea.io> Thu, 19 Feb 2026 16:27:40 +0100
nymea (1.14.1) noble; urgency=medium
[ Simon Stürz ]
* LogEngine: Improve behavior of disabled logengine
* JsonRpc Server: Improve token verification handling
* Update default loations for the mac-addresses.db to be more generic
* Thing: Add state name based set method for possible values
-- jenkins <developer@nymea.io> Tue, 10 Feb 2026 12:05:47 +0100
nymea (1.14.0) noble; urgency=medium nymea (1.14.0) noble; urgency=medium
[ Simon Stürz ] [ Simon Stürz ]

View File

@ -30,6 +30,7 @@
#include <QFileInfo> #include <QFileInfo>
#include <QTimer> #include <QTimer>
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QDir>
#include <QStandardPaths> #include <QStandardPaths>
#include <QtConcurrent/QtConcurrent> #include <QtConcurrent/QtConcurrent>
@ -41,18 +42,30 @@ MacAddressDatabase::MacAddressDatabase(QObject *parent) : QObject(parent)
{ {
// Find database in system data locations // Find database in system data locations
QString databaseFileName; QString databaseFileName;
foreach (const QString &dataLocation, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { const QStringList dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
QFileInfo databaseFileInfo(dataLocation + QDir::separator() + "mac-addresses.db"); foreach (const QString &dataLocation, dataLocations) {
if (!databaseFileInfo.exists()) { const QStringList candidateFiles = {
continue; dataLocation + QDir::separator() + "nymea" + QDir::separator() + "nymead" + QDir::separator() + "mac-addresses.db",
dataLocation + QDir::separator() + "nymea" + QDir::separator() + "mac-addresses.db",
dataLocation + QDir::separator() + "mac-addresses.db"
};
foreach (const QString &candidate, candidateFiles) {
QFileInfo databaseFileInfo(candidate);
if (!databaseFileInfo.exists())
continue;
databaseFileName = databaseFileInfo.absoluteFilePath();
break;
} }
databaseFileName = databaseFileInfo.absoluteFilePath(); if (!databaseFileName.isEmpty())
break; break;
} }
if (databaseFileName.isEmpty()) { if (databaseFileName.isEmpty()) {
qCWarning(dcMacAddressDatabase()) << "Could not find the mac address database in any system data location paths" << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); qCWarning(dcMacAddressDatabase()) << "Could not find the mac address database in any system data location paths" << dataLocations;
qCWarning(dcMacAddressDatabase()) << "The mac address database lookup feature will not be available."; qCWarning(dcMacAddressDatabase()) << "The mac address database lookup feature will not be available.";
return; return;
} }

View File

@ -50,7 +50,7 @@ private:
QSqlDatabase m_db; QSqlDatabase m_db;
bool m_available = false; bool m_available = false;
QString m_connectionName; QString m_connectionName;
QString m_databaseName = "/usr/share/nymea/mac-addresses.db"; QString m_databaseName = "/usr/share/nymea/nymead/mac-addresses.db";
MacAddressDatabaseReplyImpl *m_currentReply = nullptr; MacAddressDatabaseReplyImpl *m_currentReply = nullptr;
QFutureWatcher<QString> *m_futureWatcher = nullptr; QFutureWatcher<QString> *m_futureWatcher = nullptr;

View File

@ -789,7 +789,7 @@ JsonReply *IntegrationsHandler::GetThings(const QVariantMap &params, const JsonC
QVariantMap returns; QVariantMap returns;
QVariantList things; QVariantList things;
if (NymeaCore::instance()->userManager()->hasRestrictedThingAccess(context.token())) { if (NymeaCore::instance()->userManager()->hasRestrictedThingAccess(context.token()) && context.authenticationEnabled()) {
// Restricted things access // Restricted things access
QList<ThingId> allowedThingIds = NymeaCore::instance()->userManager()->getAllowedThingIdsForToken(context.token()); QList<ThingId> allowedThingIds = NymeaCore::instance()->userManager()->getAllowedThingIdsForToken(context.token());
if (params.contains("thingId")) { if (params.contains("thingId")) {
@ -983,7 +983,7 @@ JsonReply *IntegrationsHandler::GetStateTypes(const QVariantMap &params, const J
JsonReply *IntegrationsHandler::GetStateValue(const QVariantMap &params, const JsonContext &context) const JsonReply *IntegrationsHandler::GetStateValue(const QVariantMap &params, const JsonContext &context) const
{ {
ThingId thingId(params.value("thingId").toString()); ThingId thingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
Thing *thing = m_thingManager->findConfiguredThing(thingId); Thing *thing = m_thingManager->findConfiguredThing(thingId);
@ -1002,7 +1002,7 @@ JsonReply *IntegrationsHandler::GetStateValue(const QVariantMap &params, const J
JsonReply *IntegrationsHandler::GetStateValues(const QVariantMap &params, const JsonContext &context) const JsonReply *IntegrationsHandler::GetStateValues(const QVariantMap &params, const JsonContext &context) const
{ {
ThingId thingId(params.value("thingId").toString()); ThingId thingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
Thing *thing = m_thingManager->findConfiguredThing(thingId); Thing *thing = m_thingManager->findConfiguredThing(thingId);
@ -1017,7 +1017,7 @@ JsonReply *IntegrationsHandler::GetStateValues(const QVariantMap &params, const
JsonReply *IntegrationsHandler::BrowseThing(const QVariantMap &params, const JsonContext &context) const JsonReply *IntegrationsHandler::BrowseThing(const QVariantMap &params, const JsonContext &context) const
{ {
ThingId thingId(params.value("thingId").toString()); ThingId thingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
QString itemId = params.value("itemId").toString(); QString itemId = params.value("itemId").toString();
@ -1047,7 +1047,7 @@ JsonReply *IntegrationsHandler::BrowseThing(const QVariantMap &params, const Jso
JsonReply *IntegrationsHandler::GetBrowserItem(const QVariantMap &params, const JsonContext &context) const JsonReply *IntegrationsHandler::GetBrowserItem(const QVariantMap &params, const JsonContext &context) const
{ {
ThingId thingId(params.value("thingId").toString()); ThingId thingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
QString itemId = params.value("itemId").toString(); QString itemId = params.value("itemId").toString();
@ -1072,7 +1072,7 @@ JsonReply *IntegrationsHandler::GetBrowserItem(const QVariantMap &params, const
JsonReply *IntegrationsHandler::ExecuteAction(const QVariantMap &params, const JsonContext &context) JsonReply *IntegrationsHandler::ExecuteAction(const QVariantMap &params, const JsonContext &context)
{ {
ThingId thingId(params.value("thingId").toString()); ThingId thingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
ActionTypeId actionTypeId(params.value("actionTypeId").toString()); ActionTypeId actionTypeId(params.value("actionTypeId").toString());
@ -1101,7 +1101,7 @@ JsonReply *IntegrationsHandler::ExecuteAction(const QVariantMap &params, const J
JsonReply *IntegrationsHandler::ExecuteBrowserItem(const QVariantMap &params, const JsonContext &context) JsonReply *IntegrationsHandler::ExecuteBrowserItem(const QVariantMap &params, const JsonContext &context)
{ {
ThingId thingId = ThingId(params.value("thingId").toString()); ThingId thingId = ThingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
QString itemId = params.value("itemId").toString(); QString itemId = params.value("itemId").toString();
@ -1126,7 +1126,7 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItem(const QVariantMap &params, co
JsonReply *IntegrationsHandler::ExecuteBrowserItemAction(const QVariantMap &params, const JsonContext &context) JsonReply *IntegrationsHandler::ExecuteBrowserItemAction(const QVariantMap &params, const JsonContext &context)
{ {
ThingId thingId = ThingId(params.value("thingId").toString()); ThingId thingId = ThingId(params.value("thingId").toString());
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
QString itemId = params.value("itemId").toString(); QString itemId = params.value("itemId").toString();
@ -1153,7 +1153,7 @@ JsonReply *IntegrationsHandler::ExecuteBrowserItemAction(const QVariantMap &para
JsonReply *IntegrationsHandler::GetIOConnections(const QVariantMap &params, const JsonContext &context) JsonReply *IntegrationsHandler::GetIOConnections(const QVariantMap &params, const JsonContext &context)
{ {
ThingId thingId = params.value("thingId").toUuid(); ThingId thingId = params.value("thingId").toUuid();
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token())) if (context.authenticationEnabled() && !NymeaCore::instance()->userManager()->accessToThingGranted(thingId, context.token()))
return createReply(statusToReply(Thing::ThingErrorThingNotFound)); return createReply(statusToReply(Thing::ThingErrorThingNotFound));
IOConnections ioConnections = m_thingManager->ioConnections(thingId); IOConnections ioConnections = m_thingManager->ioConnections(thingId);

View File

@ -603,7 +603,7 @@ void JsonRPCServerImplementation::processJsonPacket(TransportInterface *interfac
} }
// check if authentication is required for this transport // check if authentication is required for this transport
if (interface->configuration().authenticationEnabled) { if (interface->configuration().authenticationEnabled) {
QStringList authExemptMethodsNoUser = {"JSONRPC.Hello", "JSONRPC.RequestPushButtonAuth", "JSONRPC.CreateUser"}; QStringList authExemptMethodsNoUser = {"JSONRPC.Hello", "JSONRPC.RequestPushButtonAuth", "JSONRPC.CreateUser"};
QStringList authExemptMethodsWithUser = {"JSONRPC.Hello", "JSONRPC.Authenticate", "JSONRPC.RequestPushButtonAuth"}; QStringList authExemptMethodsWithUser = {"JSONRPC.Hello", "JSONRPC.Authenticate", "JSONRPC.RequestPushButtonAuth"};
// if there is no user in the system yet, let's fail unless this is a special method for authentication itself // if there is no user in the system yet, let's fail unless this is a special method for authentication itself
@ -617,7 +617,7 @@ void JsonRPCServerImplementation::processJsonPacket(TransportInterface *interfac
return; return;
} }
} else { } else {
// ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect Hello call // Ok, we have a user. If there isn't a valid token, let's fail unless this is an authentication related call
if (!authExemptMethodsWithUser.contains(methodString)) { if (!authExemptMethodsWithUser.contains(methodString)) {
if (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token)) { if (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token)) {
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token."); sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
@ -681,7 +681,7 @@ void JsonRPCServerImplementation::processJsonPacket(TransportInterface *interfac
handler->setProperty("transportInterface", reinterpret_cast<qint64>(interface)); handler->setProperty("transportInterface", reinterpret_cast<qint64>(interface));
} }
JsonContext callContext(clientId, m_clientLocales.value(clientId)); JsonContext callContext(clientId, m_clientLocales.value(clientId), interface->configuration().authenticationEnabled);
callContext.setToken(token); callContext.setToken(token);
qCDebug(dcJsonRpc()) << "Invoking method" << targetNamespace + '.' + method << "from client" << clientId; qCDebug(dcJsonRpc()) << "Invoking method" << targetNamespace + '.' + method << "from client" << clientId;
@ -809,7 +809,9 @@ void JsonRPCServerImplementation::sendClientNotification(const QVariantMap &para
continue; continue;
// Make sure this client is allowed to receive this notification // Make sure this client is allowed to receive this notification
if (m_clientTokens.contains(clientId)) { TransportInterface *transport = m_clientTransports.value(clientId, nullptr);
const bool authEnabled = transport ? transport->configuration().authenticationEnabled : true;
if (authEnabled && m_clientTokens.contains(clientId)) {
const QByteArray token = m_clientTokens.value(clientId); const QByteArray token = m_clientTokens.value(clientId);
if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, token)) { if (!NymeaCore::instance()->userManager()->accessToThingGranted(thingId, token)) {
qCDebug(dcJsonRpc()) << "Not sending notification to client" << "to client" << clientId.toString() qCDebug(dcJsonRpc()) << "Not sending notification to client" << "to client" << clientId.toString()
@ -849,9 +851,24 @@ void JsonRPCServerImplementation::sendClientNotification(const QVariantMap &para
{ {
// Send client specific notifications // Send client specific notifications
qCDebug(dcJsonRpc()) << "Sending notification to client" << userInfo.username() << "connections..."; qCDebug(dcJsonRpc()) << "Sending notification to client" << userInfo.username() << "connections...";
foreach (const QByteArray &token, m_clientTokens) { for (auto it = m_clientTokens.constBegin(); it != m_clientTokens.constEnd(); ++it) {
const QUuid clientId = it.key();
const QByteArray token = it.value();
TransportInterface *transport = m_clientTransports.value(clientId, nullptr);
const bool authEnabled = transport ? transport->configuration().authenticationEnabled : true;
if (!authEnabled) {
sendClientNotification(clientId, params);
continue;
}
if (token.isEmpty()) {
continue;
}
if (NymeaCore::instance()->userManager()->tokenInfo(token).username() == userInfo.username()) { if (NymeaCore::instance()->userManager()->tokenInfo(token).username() == userInfo.username()) {
sendClientNotification(m_clientTokens.key(token), params); sendClientNotification(clientId, params);
} }
} }
} }

View File

@ -38,15 +38,18 @@ SystemHandler::SystemHandler(Platform *platform, QObject *parent):
registerObject<Package, Packages>(); registerObject<Package, Packages>();
registerObject<Repository, Repositories>(); registerObject<Repository, Repositories>();
registerEnum<PlatformUpdateController::UpdateType>();
// Methods // Methods
QString description; QVariantMap params; QVariantMap returns; QString description; QVariantMap params; QVariantMap returns;
description = "Get the list of capabilites on this system. The property \"powerManagement\" indicates whether " description = "Get the list of capabilites on this system. The property \"powerManagement\" indicates whether "
"restarting nymea and rebooting or shutting down is supported on this system. The property \"updateManagement indicates " "restarting nymea and rebooting or shutting down is supported on this system. The property \"updateManagement\" indicates "
"whether system update features are available in this system. The property \"timeManagement\" " "whether system update features are available in this system. The \"updateManagementType\" indicates which kind of update is "
"indicates whether the system time can be configured on this system. Note that GetTime will be " "supported on this platform. The property \"timeManagement\" indicates whether the system time can be configured "
"available in any case."; "on this system. Note that GetTime will be available in any case.";
returns.insert("powerManagement", enumValueName(Bool)); returns.insert("powerManagement", enumValueName(Bool));
returns.insert("updateManagement", enumValueName(Bool)); returns.insert("updateManagement", enumValueName(Bool));
returns.insert("updateManagementType", enumRef<PlatformUpdateController::UpdateType>());
returns.insert("timeManagement", enumValueName(Bool)); returns.insert("timeManagement", enumValueName(Bool));
registerMethod("GetCapabilities", description, params, returns); registerMethod("GetCapabilities", description, params, returns);
@ -66,15 +69,17 @@ SystemHandler::SystemHandler(Platform *platform, QObject *parent):
registerMethod("Shutdown", description, params, returns); registerMethod("Shutdown", description, params, returns);
params.clear(); returns.clear(); params.clear(); returns.clear();
description = "Get the current status of the update system. \"busy\" indicates that the system is current busy with " description = "Get the current status of the update system. \"busy\" indicates that the system is current busy with "
"an operation regarding updates. This does not necessarily mean an actual update is running. When this " "an operation regarding updates. This does not necessarily mean an actual update is running. When this "
"is true, update related functions on the client should be marked as busy and no interaction with update " "is true, update related functions on the client should be marked as busy and no interaction with update "
"components shall be allowed. An example for such a state is when the system queries the server if there " "components shall be allowed. An example for such a state is when the system queries the server if there "
"are updates available, typically after a call to CheckForUpdates. \"updateRunning\" on the other hand " "are updates available, typically after a call to CheckForUpdates. \"updateRunning\" on the other hand "
"indicates an actual update process is ongoing. The user should be informed about it, the system also " "indicates an actual update process is ongoing. The user should be informed about it, the system also "
"might restart at any point while an update is running."; "might restart at any point while an update is running. The \"updateProgress\" property is optional, "
"if the backend supports it, a progress >= 0 indicated the update progress in percentage.";
returns.insert("busy", enumValueName(Bool)); returns.insert("busy", enumValueName(Bool));
returns.insert("updateRunning", enumValueName(Bool)); returns.insert("updateRunning", enumValueName(Bool));
returns.insert("o:updateProgress", enumValueName(Int));
registerMethod("GetUpdateStatus", description, params, returns); registerMethod("GetUpdateStatus", description, params, returns);
params.clear(); returns.clear(); params.clear(); returns.clear();
@ -172,12 +177,14 @@ SystemHandler::SystemHandler(Platform *platform, QObject *parent):
description = "Emitted whenever the system capabilities change."; description = "Emitted whenever the system capabilities change.";
params.insert("powerManagement", enumValueName(Bool)); params.insert("powerManagement", enumValueName(Bool));
params.insert("updateManagement", enumValueName(Bool)); params.insert("updateManagement", enumValueName(Bool));
params.insert("updateManagementType", enumRef<PlatformUpdateController::UpdateType>());
registerNotification("CapabilitiesChanged", description, params); registerNotification("CapabilitiesChanged", description, params);
params.clear(); params.clear();
description = "Emitted whenever the update status changes."; description = "Emitted whenever the update status changes.";
params.insert("busy", enumValueName(Bool)); params.insert("busy", enumValueName(Bool));
params.insert("updateRunning", enumValueName(Bool)); params.insert("updateRunning", enumValueName(Bool));
params.insert("o:updateProgress", enumValueName(Int));
registerNotification("UpdateStatusChanged", description, params); registerNotification("UpdateStatusChanged", description, params);
params.clear(); params.clear();
@ -220,18 +227,17 @@ SystemHandler::SystemHandler(Platform *platform, QObject *parent):
connect(m_platform->systemController(), &PlatformSystemController::availableChanged, this, &SystemHandler::onCapabilitiesChanged); connect(m_platform->systemController(), &PlatformSystemController::availableChanged, this, &SystemHandler::onCapabilitiesChanged);
connect(m_platform->updateController(), &PlatformUpdateController::availableChanged, this, &SystemHandler::onCapabilitiesChanged); connect(m_platform->updateController(), &PlatformUpdateController::availableChanged, this, &SystemHandler::onCapabilitiesChanged);
connect(m_platform->updateController(), &PlatformUpdateController::busyChanged, this, [this](){ connect(m_platform->updateController(), &PlatformUpdateController::busyChanged, this, [this](){
QVariantMap params; emit UpdateStatusChanged(buildUpdateStatus());
params.insert("busy", m_platform->updateController()->busy());
params.insert("updateRunning", m_platform->updateController()->updateRunning());
emit UpdateStatusChanged(params);
}); });
connect(m_platform->updateController(), &PlatformUpdateController::updateRunningChanged, this, [this](){ connect(m_platform->updateController(), &PlatformUpdateController::updateRunningChanged, this, [this](){
QVariantMap params; emit UpdateStatusChanged(buildUpdateStatus());
params.insert("busy", m_platform->updateController()->busy());
params.insert("updateRunning", m_platform->updateController()->updateRunning());
emit UpdateStatusChanged(params);
}); });
connect(m_platform->updateController(), &PlatformUpdateController::updateProgressChanged, this, [this](){
emit UpdateStatusChanged(buildUpdateStatus());
});
connect(m_platform->updateController(), &PlatformUpdateController::packageAdded, this, [this](const Package &package){ connect(m_platform->updateController(), &PlatformUpdateController::packageAdded, this, [this](const Package &package){
QVariantMap params; QVariantMap params;
params.insert("package", pack(package)); params.insert("package", pack(package));
@ -247,6 +253,7 @@ SystemHandler::SystemHandler(Platform *platform, QObject *parent):
params.insert("packageId", packageId); params.insert("packageId", packageId);
emit PackageRemoved(params); emit PackageRemoved(params);
}); });
connect(m_platform->updateController(), &PlatformUpdateController::repositoryAdded, this, [this](const Repository &repository){ connect(m_platform->updateController(), &PlatformUpdateController::repositoryAdded, this, [this](const Repository &repository){
QVariantMap params; QVariantMap params;
params.insert("repository", pack(repository)); params.insert("repository", pack(repository));
@ -262,6 +269,7 @@ SystemHandler::SystemHandler(Platform *platform, QObject *parent):
params.insert("repositoryId", repositoryId); params.insert("repositoryId", repositoryId);
emit RepositoryRemoved(params); emit RepositoryRemoved(params);
}); });
connect(m_platform->systemController(), &PlatformSystemController::timeConfigurationChanged, this, [this](){ connect(m_platform->systemController(), &PlatformSystemController::timeConfigurationChanged, this, [this](){
QVariantMap params; QVariantMap params;
params.insert("time", QDateTime::currentMSecsSinceEpoch() / 1000); params.insert("time", QDateTime::currentMSecsSinceEpoch() / 1000);
@ -283,6 +291,7 @@ JsonReply *SystemHandler::GetCapabilities(const QVariantMap &params)
QVariantMap data; QVariantMap data;
data.insert("powerManagement", m_platform->systemController()->powerManagementAvailable()); data.insert("powerManagement", m_platform->systemController()->powerManagementAvailable());
data.insert("updateManagement", m_platform->updateController()->updateManagementAvailable()); data.insert("updateManagement", m_platform->updateController()->updateManagementAvailable());
data.insert("updateManagementType", enumValueName(m_platform->updateController()->updateType()));
data.insert("timeManagement", m_platform->systemController()->timeManagementAvailable()); data.insert("timeManagement", m_platform->systemController()->timeManagementAvailable());
return createReply(data); return createReply(data);
} }
@ -317,10 +326,7 @@ JsonReply *SystemHandler::Shutdown(const QVariantMap &params) const
JsonReply *SystemHandler::GetUpdateStatus(const QVariantMap &params) const JsonReply *SystemHandler::GetUpdateStatus(const QVariantMap &params) const
{ {
Q_UNUSED(params) Q_UNUSED(params)
QVariantMap ret; return createReply(buildUpdateStatus());
ret.insert("busy", m_platform->updateController()->busy());
ret.insert("updateRunning", m_platform->updateController()->updateRunning());
return createReply(ret);
} }
JsonReply *SystemHandler::CheckForUpdates(const QVariantMap &params) const JsonReply *SystemHandler::CheckForUpdates(const QVariantMap &params) const
@ -459,7 +465,19 @@ void SystemHandler::onCapabilitiesChanged()
QVariantMap caps; QVariantMap caps;
caps.insert("powerManagement", m_platform->systemController()->powerManagementAvailable()); caps.insert("powerManagement", m_platform->systemController()->powerManagementAvailable());
caps.insert("updateManagement", m_platform->updateController()->updateManagementAvailable()); caps.insert("updateManagement", m_platform->updateController()->updateManagementAvailable());
caps.insert("updateManagementType", enumValueName(m_platform->updateController()->updateType()));
emit CapabilitiesChanged(caps); emit CapabilitiesChanged(caps);
} }
QVariantMap SystemHandler::buildUpdateStatus() const
{
QVariantMap params;
params.insert("busy", m_platform->updateController()->busy());
params.insert("updateRunning", m_platform->updateController()->updateRunning());
if (m_platform->updateController()->updateProgress() >= 0)
params.insert("updateProgress", m_platform->updateController()->updateProgress());
return params;
}
} }

View File

@ -82,6 +82,8 @@ private slots:
private: private:
Platform *m_platform = nullptr; Platform *m_platform = nullptr;
QVariantMap buildUpdateStatus() const;
}; };
} }

View File

@ -189,15 +189,21 @@ JsonReply *UsersHandler::ChangePassword(const QVariantMap &params, const JsonCon
QVariantMap returns; QVariantMap returns;
QByteArray currentToken = context.token(); QByteArray currentToken = context.token();
if (currentToken.isEmpty()) { if (context.authenticationEnabled()) {
qCWarning(dcJsonRpc()) << "Cannot change password from an unauthenticated connection"; if (currentToken.isEmpty()) {
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); qCWarning(dcJsonRpc()) << "Cannot change password from an unauthenticated connection";
return createReply(returns); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
} return createReply(returns);
}
if (!m_userManager->verifyToken(currentToken)) { if (!m_userManager->verifyToken(currentToken)) {
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token // Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?"; qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot change password without token even if authentication is disabled for the transport";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns); return createReply(returns);
} }
@ -216,15 +222,21 @@ JsonReply *UsersHandler::ChangeUserPassword(const QVariantMap &params, const Jso
QVariantMap returns; QVariantMap returns;
QByteArray currentToken = context.token(); QByteArray currentToken = context.token();
if (currentToken.isEmpty()) { if (context.authenticationEnabled()) {
qCWarning(dcJsonRpc()) << "Cannot change a user password from an unauthenticated connection"; if (currentToken.isEmpty()) {
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); qCWarning(dcJsonRpc()) << "Cannot change a user password from an unauthenticated connection";
return createReply(returns); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
} return createReply(returns);
}
if (!m_userManager->verifyToken(currentToken)) { if (!m_userManager->verifyToken(currentToken)) {
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token // Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
qCWarning(dcJsonRpc()) << "Invalid token. Cannot change a user password from an unauthenticated connection"; qCWarning(dcJsonRpc()) << "Invalid token. Cannot change a user password from an unauthenticated connection";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot change a user password without token even if authentication is disabled for the transport";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns); return createReply(returns);
} }
@ -244,15 +256,21 @@ JsonReply *UsersHandler::GetUserInfo(const QVariantMap &params, const JsonContex
QVariantMap returns; QVariantMap returns;
QByteArray currentToken = context.token(); QByteArray currentToken = context.token();
if (currentToken.isEmpty()) { if (context.authenticationEnabled()) {
qCWarning(dcJsonRpc()) << "Cannot get user info from an unauthenticated connection"; if (currentToken.isEmpty()) {
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); qCWarning(dcJsonRpc()) << "Cannot get user info from an unauthenticated connection";
return createReply(returns); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
} return createReply(returns);
}
if (!m_userManager->verifyToken(currentToken)) { if (!m_userManager->verifyToken(currentToken)) {
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token // Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?"; qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot get user info without token even if authentication is disabled for the transport";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns); return createReply(returns);
} }
@ -272,15 +290,21 @@ JsonReply *UsersHandler::GetTokens(const QVariantMap &params, const JsonContext
QVariantMap returns; QVariantMap returns;
QByteArray currentToken = context.token(); QByteArray currentToken = context.token();
if (currentToken.isEmpty()) { if (context.authenticationEnabled()) {
qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection"; if (currentToken.isEmpty()) {
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection";
return createReply(returns); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
} return createReply(returns);
}
if (!m_userManager->verifyToken(currentToken)) { if (!m_userManager->verifyToken(currentToken)) {
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token // Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?"; qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot fetch tokens without token even if authentication is disabled for the transport";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns); return createReply(returns);
} }
@ -302,15 +326,21 @@ JsonReply *UsersHandler::GetUserTokens(const QVariantMap &params, const JsonCont
QVariantMap returns; QVariantMap returns;
QByteArray currentToken = context.token(); QByteArray currentToken = context.token();
if (currentToken.isEmpty()) { if (context.authenticationEnabled()) {
qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection"; if (currentToken.isEmpty()) {
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); qCWarning(dcJsonRpc()) << "Cannot fetch tokens for an unauthenticated connection";
return createReply(returns); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
} return createReply(returns);
}
if (!m_userManager->verifyToken(currentToken)) { if (!m_userManager->verifyToken(currentToken)) {
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token // Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?"; qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot fetch tokens without token even if authentication is disabled for the transport";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns); return createReply(returns);
} }
@ -333,15 +363,21 @@ JsonReply *UsersHandler::RemoveToken(const QVariantMap &params, const JsonContex
QVariantMap returns; QVariantMap returns;
QByteArray currentToken = context.token(); QByteArray currentToken = context.token();
if (currentToken.isEmpty()) { if (context.authenticationEnabled()) {
qCWarning(dcJsonRpc()) << "Cannot remove a token from an unauthenticated connection."; if (currentToken.isEmpty()) {
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); qCWarning(dcJsonRpc()) << "Cannot remove a token from an unauthenticated connection.";
return createReply(returns); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
} return createReply(returns);
}
if (!m_userManager->verifyToken(currentToken)) { if (!m_userManager->verifyToken(currentToken)) {
// Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token // Might happen if the client is connecting via an unauthenticated connection but tries to sneak in an invalid token
qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?"; qCWarning(dcJsonRpc()) << "Invalid token. Is this an unauthenticated connection?";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot remove a token without token even if authentication is disabled for the transport.";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied)); returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns); return createReply(returns);
} }
@ -414,7 +450,20 @@ JsonReply *UsersHandler::SetUserInfo(const QVariantMap &params, const JsonContex
{ {
QVariantMap returns; QVariantMap returns;
TokenInfo callingTokenInfo = m_userManager->tokenInfo(context.token()); QByteArray currentToken = context.token();
if (context.authenticationEnabled()) {
if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot set user info from an unauthenticated connection";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
} else if (currentToken.isEmpty()) {
qCWarning(dcJsonRpc()) << "Cannot set user info without token even if authentication is disabled for the transport";
returns.insert("error", enumValueName<UserManager::UserError>(UserManager::UserErrorPermissionDenied));
return createReply(returns);
}
TokenInfo callingTokenInfo = m_userManager->tokenInfo(currentToken);
QString username; QString username;
if (params.contains("username")) { if (params.contains("username")) {

View File

@ -24,18 +24,18 @@
#include "logengineinfluxdb.h" #include "logengineinfluxdb.h"
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QCoreApplication> #include <QCoreApplication>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrlQuery>
LogEngineInfluxDB::LogEngineInfluxDB(const QString &host, const QString &dbName, const QString &username, const QString &password, QObject *parent) LogEngineInfluxDB::LogEngineInfluxDB(const QString &host, const QString &dbName, const QString &username, const QString &password, QObject *parent)
: LogEngine{parent}, : LogEngine{parent}
m_host(host), , m_host(host)
m_dbName(dbName), , m_dbName(dbName)
m_username(username), , m_username(username)
m_password(password) , m_password(password)
{ {
m_nam = new QNetworkAccessManager(this); m_nam = new QNetworkAccessManager(this);
@ -53,7 +53,7 @@ LogEngineInfluxDB::~LogEngineInfluxDB()
qCInfo(dcLogEngine()) << "Waiting for" << (m_initQueryQueue.count() + m_queryQueue.count() + m_writeQueue.count()) << "jobs to finish... Init status:" << m_initStatus; qCInfo(dcLogEngine()) << "Waiting for" << (m_initQueryQueue.count() + m_queryQueue.count() + m_writeQueue.count()) << "jobs to finish... Init status:" << m_initStatus;
} }
while (jobsRunning()) { while (jobsRunning()) {
// qCDebug(dcLogEngine()) << "Waiting for logs to finish processing." << m_writeQueue.count() << "jobs pending..."; // qCDebug(dcLogEngine()) << "Waiting for logs to finish processing." << m_writeQueue.count() << "jobs pending...";
processQueues(); processQueues();
qApp->processEvents(); qApp->processEvents();
} }
@ -66,12 +66,12 @@ Logger *LogEngineInfluxDB::registerLogSource(const QString &name, const QStringL
return nullptr; return nullptr;
} }
// qCDebug(dcLogEngine()) << "Registering log source" << name << "with tags" << tagNames; // qCDebug(dcLogEngine()) << "Registering log source" << name << "with tags" << tagNames;
Logger *logger = createLogger(name, tagNames, loggingType); Logger *logger = createLogger(name, tagNames, loggingType);
m_loggers.insert(name, logger); m_loggers.insert(name, logger);
if (loggingType == Types::LoggingTypeSampled) { if (m_initStatus != InitStatusDisabled && loggingType == Types::LoggingTypeSampled) {
qCDebug(dcLogEngine()) << "Setting up log sampling on" << sampleColumn; qCDebug(dcLogEngine()) << "Setting up log sampling on" << sampleColumn;
if (sampleColumn.isEmpty()) { if (sampleColumn.isEmpty()) {
@ -84,55 +84,45 @@ Logger *LogEngineInfluxDB::registerLogSource(const QString &name, const QStringL
columns.append(QString("MEAN(\"%1\") AS %1").arg(sampleColumn)); columns.append(QString("MEAN(\"%1\") AS %1").arg(sampleColumn));
QString target = columns.join(", "); QString target = columns.join(", ");
QueryJob *minutesJob = query(QString("CREATE CONTINUOUS QUERY \"minutes-%1\" " QueryJob *minutesJob = query(
"ON \"nymea\" " QString(
"BEGIN " "CREATE CONTINUOUS QUERY \"minutes-%1\" " "ON \"nymea\" " "BEGIN " "SELECT %2 " "INTO minutes.\"%1\" " "FROM live.\"%1\" " "GROUP BY time(1m) " "fill(previous)" " " "END")
"SELECT %2 " .arg(name)
"INTO minutes.\"%1\" " .arg(target),
"FROM live.\"%1\" " true);
"GROUP BY time(1m) " connect(minutesJob, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &response) {
"fill(previous) "
"END").arg(name).arg(target), true);
connect(minutesJob, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &response){
if (status == QNetworkReply::NoError) { if (status == QNetworkReply::NoError) {
qCDebug(dcLogEngine()) << "Created minute based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson()); qCDebug(dcLogEngine()) << "Created minute based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
} else { } else {
qCWarning(dcLogEngine()) << "Unable to create minute based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson()); qCWarning(dcLogEngine()) << "Unable to create minute based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
} }
}); });
QueryJob *hoursJob = query(QString("CREATE CONTINUOUS QUERY \"hours-%1\" " QueryJob *hoursJob = query(
"ON \"nymea\" " QString(
"BEGIN " "CREATE CONTINUOUS QUERY \"hours-%1\" " "ON \"nymea\" " "BEGIN " "SELECT %2 " "INTO hours.\"%1\" " "FROM minutes.\"%1\" " "GROUP BY time(1h) " "fill(previous) " "END")
"SELECT %2 " .arg(name)
"INTO hours.\"%1\" " .arg(target),
"FROM minutes.\"%1\" " true);
"GROUP BY time(1h) " connect(hoursJob, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &response) {
"fill(previous) "
"END").arg(name).arg(target), true);
connect(hoursJob, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &response){
if (status == QNetworkReply::NoError) { if (status == QNetworkReply::NoError) {
qCDebug(dcLogEngine()) << "Created hour based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson()); qCDebug(dcLogEngine()) << "Created hour based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
} else { } else {
qCWarning(dcLogEngine()) << "Unable to create hour based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson()); qCWarning(dcLogEngine()) << "Unable to create hour based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
} }
}); });
QueryJob *daysJob = query(QString("CREATE CONTINUOUS QUERY \"days-%1\" " QueryJob *daysJob = query(
"ON \"nymea\" " QString(
"BEGIN " "CREATE CONTINUOUS QUERY \"days-%1\" " "ON \"nymea\" " "BEGIN " "SELECT %2 " "INTO days.\"%1\" " "FROM hours.\"%1\" " "GROUP BY time(24h) " "fill(previous) " "END")
"SELECT %2 " .arg(name)
"INTO days.\"%1\" " .arg(target),
"FROM hours.\"%1\" " true);
"GROUP BY time(24h) " connect(daysJob, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &response) {
"fill(previous) "
"END").arg(name).arg(target), true);
connect(daysJob, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &response){
if (status == QNetworkReply::NoError) { if (status == QNetworkReply::NoError) {
qCDebug(dcLogEngine()) << "Created day based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson()); qCDebug(dcLogEngine()) << "Created day based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
} else { } else {
qCWarning(dcLogEngine()) << "Unable to create days based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson()); qCWarning(dcLogEngine()) << "Unable to create days based continuous query for" << name << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
} }
}); });
} }
} }
@ -147,12 +137,15 @@ void LogEngineInfluxDB::unregisterLogSource(const QString &name)
return; return;
} }
if (m_initStatus == InitStatusDisabled)
return;
QString queryString = QString("DROP MEASUREMENT \"%1\"").arg(name); QString queryString = QString("DROP MEASUREMENT \"%1\"").arg(name);
if (m_initStatus == InitStatusOK) if (m_initStatus == InitStatusOK)
qCDebug(dcLogEngine()) << "Removing log entries:" << queryString; qCDebug(dcLogEngine()) << "Removing log entries:" << queryString;
QueryJob *job = query(queryString); QueryJob *job = query(queryString);
connect(job, &QueryJob::finished, this, [name](bool success){ connect(job, &QueryJob::finished, this, [name](bool success) {
if (success) { if (success) {
qCDebug(dcLogEngine()) << "Removed log entries for source" << name; qCDebug(dcLogEngine()) << "Removed log entries for source" << name;
} else { } else {
@ -163,6 +156,9 @@ void LogEngineInfluxDB::unregisterLogSource(const QString &name)
void LogEngineInfluxDB::logEvent(Logger *logger, const QStringList &tags, const QVariantMap &values) void LogEngineInfluxDB::logEvent(Logger *logger, const QStringList &tags, const QVariantMap &values)
{ {
if (m_initStatus == InitStatusDisabled)
return;
QString measurement = logger->name(); QString measurement = logger->name();
QStringList tagsList; QStringList tagsList;
QStringList fieldsList; QStringList fieldsList;
@ -233,7 +229,7 @@ void LogEngineInfluxDB::processQueues()
return; return;
} }
// qCDebug(dcLogEngine()) << "Processing queue:" << m_initStatus << "init count:" << m_initQueryQueue.count() << "query count:" << m_queryQueue.count() << "write count:" << m_writeQueue.count(); // qCDebug(dcLogEngine()) << "Processing queue:" << m_initStatus << "init count:" << m_initQueryQueue.count() << "query count:" << m_queryQueue.count() << "write count:" << m_writeQueue.count();
if (!m_currentInitQuery && !m_initQueryQueue.isEmpty()) { if (!m_currentInitQuery && !m_initQueryQueue.isEmpty()) {
QueryJob *job = m_initQueryQueue.takeFirst(); QueryJob *job = m_initQueryQueue.takeFirst();
@ -248,10 +244,14 @@ void LogEngineInfluxDB::processQueues()
m_currentInitQuery = job; m_currentInitQuery = job;
connect(reply, &QNetworkReply::finished, job, [=](){ connect(reply, &QNetworkReply::finished, job, [=]() {
m_currentInitQuery = nullptr; m_currentInitQuery = nullptr;
qCDebug(dcLogEngine()) << "Init query job finished"; qCDebug(dcLogEngine()) << "Init query job finished";
reply->deleteLater(); reply->deleteLater();
if (m_initStatus == InitStatusDisabled) {
job->finish(reply->error());
return;
}
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) { if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
qCWarning(dcLogEngine()) << "Influx DB protocol error:" << reply->readAll(); qCWarning(dcLogEngine()) << "Influx DB protocol error:" << reply->readAll();
job->finish(reply->error()); job->finish(reply->error());
@ -259,6 +259,10 @@ void LogEngineInfluxDB::processQueues()
} }
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
if (m_initStatus == InitStatusDisabled) {
job->finish(reply->error());
return;
}
qCWarning(dcLogEngine()) << "Error in influxdb communication:" << reply->error() << reply->errorString() << "for query:" << job->m_request.url().toString(); qCWarning(dcLogEngine()) << "Error in influxdb communication:" << reply->error() << reply->errorString() << "for query:" << job->m_request.url().toString();
job->finish(reply->error()); job->finish(reply->error());
return; return;
@ -276,7 +280,7 @@ void LogEngineInfluxDB::processQueues()
}); });
} }
if (m_initStatus != InitStatusOK ) { if (m_initStatus != InitStatusOK) {
return; return;
} }
@ -294,10 +298,15 @@ void LogEngineInfluxDB::processQueues()
m_currentQuery = job; m_currentQuery = job;
connect(reply, &QNetworkReply::finished, job, [=](){ connect(reply, &QNetworkReply::finished, job, [=]() {
qCDebug(dcLogEngine()) << "Query finished"; qCDebug(dcLogEngine()) << "Query finished";
m_currentQuery = nullptr; m_currentQuery = nullptr;
reply->deleteLater(); reply->deleteLater();
if (m_initStatus == InitStatusDisabled) {
job->finish(reply->error());
processQueues();
return;
}
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) { if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
qCWarning(dcLogEngine()) << "Influx DB protocol error:" << reply->readAll(); qCWarning(dcLogEngine()) << "Influx DB protocol error:" << reply->readAll();
job->finish(reply->error()); job->finish(reply->error());
@ -306,6 +315,11 @@ void LogEngineInfluxDB::processQueues()
} }
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
if (m_initStatus == InitStatusDisabled) {
job->finish(reply->error());
processQueues();
return;
}
qCWarning(dcLogEngine()) << "Error in influxdb communication:" << reply->error() << reply->errorString(); qCWarning(dcLogEngine()) << "Error in influxdb communication:" << reply->error() << reply->errorString();
job->finish(reply->error()); job->finish(reply->error());
processQueues(); processQueues();
@ -320,7 +334,7 @@ void LogEngineInfluxDB::processQueues()
processQueues(); processQueues();
return; return;
} }
// qCDebug(dcLogEngine()) << "Reply" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); // qCDebug(dcLogEngine()) << "Reply" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
job->finish(QNetworkReply::NoError, jsonDoc.toVariant().toMap().value("results").toList()); job->finish(QNetworkReply::NoError, jsonDoc.toVariant().toMap().value("results").toList());
@ -341,11 +355,19 @@ void LogEngineInfluxDB::processQueues()
qCDebug(dcLogEngine()) << "Started:" << reply->isRunning() << reply->isFinished(); qCDebug(dcLogEngine()) << "Started:" << reply->isRunning() << reply->isFinished();
m_currentWriteReply = reply; m_currentWriteReply = reply;
connect(reply, &QNetworkReply::finished, this, [=](){ connect(reply, &QNetworkReply::finished, this, [=]() {
m_currentWriteReply = nullptr; m_currentWriteReply = nullptr;
reply->deleteLater(); reply->deleteLater();
if (m_initStatus == InitStatusDisabled) {
processQueues();
return;
}
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
if (m_initStatus == InitStatusDisabled) {
processQueues();
return;
}
qCWarning(dcLogEngine()) << "Unable to connect to influxdb. Cannot log events." << reply->error() << reply->readAll(); qCWarning(dcLogEngine()) << "Unable to connect to influxdb. Cannot log events." << reply->error() << reply->readAll();
processQueues(); processQueues();
return; return;
@ -362,10 +384,23 @@ void LogEngineInfluxDB::processQueues()
} }
} }
LogFetchJob *LogEngineInfluxDB::fetchLogEntries(const QStringList &sources, const QStringList &columns, const QDateTime &startTime, const QDateTime &endTime, const QVariantMap &filter, Types::SampleRate sampleRate, Qt::SortOrder sortOrder, int offset, int limit) LogFetchJob *LogEngineInfluxDB::fetchLogEntries(const QStringList &sources,
const QStringList &columns,
const QDateTime &startTime,
const QDateTime &endTime,
const QVariantMap &filter,
Types::SampleRate sampleRate,
Qt::SortOrder sortOrder,
int offset,
int limit)
{ {
LogFetchJob *job = new LogFetchJob(this); LogFetchJob *job = new LogFetchJob(this);
if (m_initStatus == InitStatusDisabled) {
finishFetchJob(job, LogEntries());
return job;
}
// FIXME: injection attacks possible? // FIXME: injection attacks possible?
QString what = "*"; QString what = "*";
if (sampleRate == Types::SampleRateAny) { if (sampleRate == Types::SampleRateAny) {
@ -386,7 +421,6 @@ LogFetchJob *LogEngineInfluxDB::fetchLogEntries(const QStringList &sources, cons
QStringList escapedSourced; QStringList escapedSourced;
foreach (const QString &source, sources) { foreach (const QString &source, sources) {
QString retentionPolicy; QString retentionPolicy;
switch (sampleRate) { switch (sampleRate) {
case Types::SampleRate1Min: case Types::SampleRate1Min:
@ -456,9 +490,17 @@ LogFetchJob *LogEngineInfluxDB::fetchLogEntries(const QStringList &sources, cons
QNetworkRequest request = createQueryRequest(query); QNetworkRequest request = createQueryRequest(query);
qCDebug(dcLogEngine()) << "Request:" << request.url() << filter; qCDebug(dcLogEngine()) << "Request:" << request.url() << filter;
QNetworkReply *reply = m_nam->get(request); QNetworkReply *reply = m_nam->get(request);
connect(reply, &QNetworkReply::finished, this, [=](){ connect(reply, &QNetworkReply::finished, this, [=]() {
reply->deleteLater(); reply->deleteLater();
if (m_initStatus == InitStatusDisabled) {
finishFetchJob(job, QList<LogEntry>());
return;
}
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
if (m_initStatus == InitStatusDisabled) {
finishFetchJob(job, QList<LogEntry>());
return;
}
qCWarning(dcLogEngine()) << "Unable to obtain entries from influxdb" << reply->error() << reply->readAll(); qCWarning(dcLogEngine()) << "Unable to obtain entries from influxdb" << reply->error() << reply->readAll();
finishFetchJob(job, QList<LogEntry>()); finishFetchJob(job, QList<LogEntry>());
return; return;
@ -507,20 +549,18 @@ LogFetchJob *LogEngineInfluxDB::fetchLogEntries(const QStringList &sources, cons
bool LogEngineInfluxDB::jobsRunning() const bool LogEngineInfluxDB::jobsRunning() const
{ {
// qCDebug(dcLogEngine()) << "Jobs running:" << m_initStatus << m_writeQueue.count() << m_initQueryQueue.count() << m_queryQueue.count() << m_currentWriteReply; // qCDebug(dcLogEngine()) << "Jobs running:" << m_initStatus << m_writeQueue.count() << m_initQueryQueue.count() << m_queryQueue.count() << m_currentWriteReply;
return m_currentInitQuery return m_currentInitQuery || !m_initQueryQueue.isEmpty() || m_currentQuery || !m_queryQueue.isEmpty() || m_currentWriteReply || !m_writeQueue.isEmpty();
|| !m_initQueryQueue.isEmpty()
|| m_currentQuery
|| !m_queryQueue.isEmpty()
|| m_currentWriteReply
|| !m_writeQueue.isEmpty();
} }
void LogEngineInfluxDB::clear(const QString &source) void LogEngineInfluxDB::clear(const QString &source)
{ {
if (m_initStatus == InitStatusDisabled)
return;
qCDebug(dcLogEngine()) << "Clearing entries for source:" << source; qCDebug(dcLogEngine()) << "Clearing entries for source:" << source;
QueryJob *job = query(QString("DROP MEASUREMENT \"%1\"").arg(source)); QueryJob *job = query(QString("DROP MEASUREMENT \"%1\"").arg(source));
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &results){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &results) {
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCWarning(dcLogEngine()) << "Unable to clear log entries for" << source << ":" << qUtf8Printable(QJsonDocument::fromVariant(results).toJson()); qCWarning(dcLogEngine()) << "Unable to clear log entries for" << source << ":" << qUtf8Printable(QJsonDocument::fromVariant(results).toJson());
} }
@ -552,8 +592,14 @@ void LogEngineInfluxDB::initDB()
void LogEngineInfluxDB::createDB() void LogEngineInfluxDB::createDB()
{ {
if (m_initStatus == InitStatusDisabled)
return;
QueryJob *job = query("SHOW DATABASES", false, true); QueryJob *job = query("SHOW DATABASES", false, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &results){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &results) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
if (status == QNetworkReply::ConnectionRefusedError) { if (status == QNetworkReply::ConnectionRefusedError) {
// Influx not up yet? trying again in 5 secs... // Influx not up yet? trying again in 5 secs...
@ -563,6 +609,10 @@ void LogEngineInfluxDB::createDB()
} }
return; return;
} }
if (m_initStatus == InitStatusDisabled)
return;
qCCritical(dcLogEngine()) << "Unable to connect to InfluxDB"; qCCritical(dcLogEngine()) << "Unable to connect to InfluxDB";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
return; return;
@ -604,6 +654,9 @@ void LogEngineInfluxDB::createDB()
QueryJob *job = query(QString("CREATE DATABASE %1").arg(m_dbName), true, true); QueryJob *job = query(QString("CREATE DATABASE %1").arg(m_dbName), true, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &result) { connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &result) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCCritical(dcLogEngine()) << "Unable to create" << m_dbName << "database in influxdb:" << QJsonDocument::fromVariant(result).toJson(); qCCritical(dcLogEngine()) << "Unable to create" << m_dbName << "database in influxdb:" << QJsonDocument::fromVariant(result).toJson();
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -617,8 +670,14 @@ void LogEngineInfluxDB::createDB()
void LogEngineInfluxDB::createRetentionPolicies() void LogEngineInfluxDB::createRetentionPolicies()
{ {
if (m_initStatus == InitStatusDisabled)
return;
QueryJob *job = query("SHOW RETENTION POLICIES", false, true); QueryJob *job = query("SHOW RETENTION POLICIES", false, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &results){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status, const QVariantList &results) {
if (m_initStatus == InitStatusDisabled) {
return;
}
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCCritical(dcLogEngine()) << "Unable to query retention policies."; qCCritical(dcLogEngine()) << "Unable to query retention policies.";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -679,7 +738,10 @@ void LogEngineInfluxDB::createRetentionPolicies()
if (!discreteRPFound) { if (!discreteRPFound) {
qCInfo(dcLogEngine()) << "Creating discrete nymea retention policy in influxdb"; qCInfo(dcLogEngine()) << "Creating discrete nymea retention policy in influxdb";
QueryJob *job = query(QString("CREATE RETENTION POLICY discrete ON %1 DURATION 8760h REPLICATION 1").arg(m_dbName), true, true); QueryJob *job = query(QString("CREATE RETENTION POLICY discrete ON %1 DURATION 8760h REPLICATION 1").arg(m_dbName), true, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCWarning(dcLogEngine()) << "Unable to create discrete retention policy in influxdb."; qCWarning(dcLogEngine()) << "Unable to create discrete retention policy in influxdb.";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -693,7 +755,10 @@ void LogEngineInfluxDB::createRetentionPolicies()
if (!liveRPFound) { if (!liveRPFound) {
qCInfo(dcLogEngine()) << "Creating live nymea retention policy in influxdb"; qCInfo(dcLogEngine()) << "Creating live nymea retention policy in influxdb";
QueryJob *job = query(QString("CREATE RETENTION POLICY live ON %1 DURATION 24h REPLICATION 1").arg(m_dbName), true, true); QueryJob *job = query(QString("CREATE RETENTION POLICY live ON %1 DURATION 24h REPLICATION 1").arg(m_dbName), true, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCWarning(dcLogEngine()) << "Unable to create live retention policy in influxdb."; qCWarning(dcLogEngine()) << "Unable to create live retention policy in influxdb.";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -707,7 +772,10 @@ void LogEngineInfluxDB::createRetentionPolicies()
if (!minutesRPFound) { if (!minutesRPFound) {
qCInfo(dcLogEngine()) << "Creating minutes nymea retention policy in influxdb"; qCInfo(dcLogEngine()) << "Creating minutes nymea retention policy in influxdb";
QueryJob *job = query(QString("CREATE RETENTION POLICY minutes ON %1 DURATION 168h REPLICATION 1").arg(m_dbName), true, true); QueryJob *job = query(QString("CREATE RETENTION POLICY minutes ON %1 DURATION 168h REPLICATION 1").arg(m_dbName), true, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCWarning(dcLogEngine()) << "Unable to create minutes retention policy in influxdb."; qCWarning(dcLogEngine()) << "Unable to create minutes retention policy in influxdb.";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -721,7 +789,10 @@ void LogEngineInfluxDB::createRetentionPolicies()
if (!hoursRPFound) { if (!hoursRPFound) {
qCInfo(dcLogEngine()) << "Creating hours nymea retention policy in influxdb"; qCInfo(dcLogEngine()) << "Creating hours nymea retention policy in influxdb";
QueryJob *job = query(QString("CREATE RETENTION POLICY hours ON %1 DURATION 26280h REPLICATION 1").arg(m_dbName), true, true); QueryJob *job = query(QString("CREATE RETENTION POLICY hours ON %1 DURATION 26280h REPLICATION 1").arg(m_dbName), true, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCWarning(dcLogEngine()) << "Unable to create hours retention policy in influxdb."; qCWarning(dcLogEngine()) << "Unable to create hours retention policy in influxdb.";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -735,7 +806,10 @@ void LogEngineInfluxDB::createRetentionPolicies()
if (!daysRPFound) { if (!daysRPFound) {
qCInfo(dcLogEngine()) << "Creating days nymea retention policy in influxdb"; qCInfo(dcLogEngine()) << "Creating days nymea retention policy in influxdb";
QueryJob *job = query(QString("CREATE RETENTION POLICY days ON %1 DURATION 175200h REPLICATION 1").arg(m_dbName), true, true); QueryJob *job = query(QString("CREATE RETENTION POLICY days ON %1 DURATION 175200h REPLICATION 1").arg(m_dbName), true, true);
connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status){ connect(job, &QueryJob::finished, this, [=](QNetworkReply::NetworkError status) {
if (m_initStatus == InitStatusDisabled)
return;
if (status != QNetworkReply::NoError) { if (status != QNetworkReply::NoError) {
qCWarning(dcLogEngine()) << "Unable to create days retention policy in influxdb."; qCWarning(dcLogEngine()) << "Unable to create days retention policy in influxdb.";
m_initStatus = InitStatusFailure; m_initStatus = InitStatusFailure;
@ -748,7 +822,8 @@ void LogEngineInfluxDB::createRetentionPolicies()
m_initStatus = InitStatusOK; m_initStatus = InitStatusOK;
qCDebug(dcLogEngine()) << "Influx initialized. Starting to process log entries (" << m_initQueryQueue.count() << m_queryQueue.count() << m_writeQueue.count() << "in queue)"; qCDebug(dcLogEngine()) << "Influx initialized. Starting to process log entries (" << m_initQueryQueue.count() << m_queryQueue.count() << m_writeQueue.count()
<< "in queue)";
processQueues(); processQueues();
}); });
} }
@ -819,18 +894,15 @@ QueryJob *LogEngineInfluxDB::query(const QString &query, bool post, bool isInit)
return job; return job;
} }
QueryJob::QueryJob(const QNetworkRequest &request, bool post, bool isInit, QObject *parent): QueryJob::QueryJob(const QNetworkRequest &request, bool post, bool isInit, QObject *parent)
QObject(parent), : QObject(parent)
m_request(request), , m_request(request)
m_post(post), , m_post(post)
m_isInit(isInit) , m_isInit(isInit)
{ {}
}
void QueryJob::finish(QNetworkReply::NetworkError status, const QVariantList &results) void QueryJob::finish(QNetworkReply::NetworkError status, const QVariantList &results)
{ {
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, status), Q_ARG(QVariantList, results)); QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection, Q_ARG(QNetworkReply::NetworkError, status), Q_ARG(QVariantList, results));
QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection);
} }

View File

@ -534,7 +534,7 @@ QList<TokenInfo> UserManager::tokens(const QString &username) const
TokenInfo UserManager::tokenInfo(const QByteArray &token) const TokenInfo UserManager::tokenInfo(const QByteArray &token) const
{ {
if (!validateToken(token)) { if (!validateToken(token)) {
qCWarning(dcUserManager) << "Token did not pass validation:" << token; qCWarning(dcUserManager()) << "Token did not pass validation:" << token;
return TokenInfo(); return TokenInfo();
} }

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of nymea. * This file is part of nymea.
* *
@ -25,10 +25,11 @@
#ifndef BROWSERESULT_H #ifndef BROWSERESULT_H
#define BROWSERESULT_H #define BROWSERESULT_H
#include <QObject>
#include <QLocale> #include <QLocale>
#include <QObject>
#include "thing.h" #include "thing.h"
#include "types/browseritem.h"
class ThingManager; class ThingManager;
@ -38,7 +39,7 @@ class BrowseResult : public QObject
public: public:
explicit BrowseResult(Thing *thing, ThingManager *thingManager, const QString &itemId, const QLocale &locale, QObject *parent, quint32 timeout = 0); explicit BrowseResult(Thing *thing, ThingManager *thingManager, const QString &itemId, const QLocale &locale, QObject *parent, quint32 timeout = 0);
Thing* thing() const; Thing *thing() const;
QString itemId() const; QString itemId() const;
QLocale locale() const; QLocale locale() const;

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of nymea. * This file is part of nymea.
* *
@ -25,10 +25,11 @@
#ifndef BROWSERITEMRESULT_H #ifndef BROWSERITEMRESULT_H
#define BROWSERITEMRESULT_H #define BROWSERITEMRESULT_H
#include <QObject>
#include <QLocale> #include <QLocale>
#include <QObject>
#include "thing.h" #include "thing.h"
#include "types/browseritem.h"
class ThingManager; class ThingManager;
@ -39,7 +40,7 @@ class BrowserItemResult : public QObject
public: public:
explicit BrowserItemResult(Thing *thing, ThingManager *thingManager, const QString &itemId, const QLocale &locale, QObject *parent, quint32 timeout = 0); explicit BrowserItemResult(Thing *thing, ThingManager *thingManager, const QString &itemId, const QLocale &locale, QObject *parent, quint32 timeout = 0);
Thing* thing() const; Thing *thing() const;
QString itemId() const; QString itemId() const;
QLocale locale() const; QLocale locale() const;

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of nymea. * This file is part of nymea.
* *
@ -313,7 +313,6 @@ States Thing::states() const
return m_states; return m_states;
} }
/*! Returns true, a \l{Param} with the given \a paramTypeId exists for this thing. */ /*! Returns true, a \l{Param} with the given \a paramTypeId exists for this thing. */
bool Thing::hasParam(const QString &paramName) const bool Thing::hasParam(const QString &paramName) const
{ {
@ -321,7 +320,6 @@ bool Thing::hasParam(const QString &paramName) const
return m_params.hasParam(paramTypeId); return m_params.hasParam(paramTypeId);
} }
/*! Returns true, a \l{Param} with the given \a paramTypeId exists for this thing. */ /*! Returns true, a \l{Param} with the given \a paramTypeId exists for this thing. */
bool Thing::hasParam(const ParamTypeId &paramTypeId) const bool Thing::hasParam(const ParamTypeId &paramTypeId) const
{ {
@ -516,7 +514,7 @@ void Thing::setStateValue(const QString &stateName, const QVariant &value)
setStateValue(stateTypeId, value); setStateValue(stateTypeId, value);
} }
/*! Sets the minimum value for the \l{State} matching the given \a stateTypeId in this thing to value. */ /*! Sets the minimum value for the \l{State} matching the given \a stateTypeId in this thing to \a minValue. */
void Thing::setStateMinValue(const StateTypeId &stateTypeId, const QVariant &minValue) void Thing::setStateMinValue(const StateTypeId &stateTypeId, const QVariant &minValue)
{ {
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId); StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
@ -559,14 +557,14 @@ void Thing::setStateMinValue(const StateTypeId &stateTypeId, const QVariant &min
qCWarning(dcThing()).nospace() << this << ": Failed setting minimum state value " << stateType.name() << " to " << minValue; qCWarning(dcThing()).nospace() << this << ": Failed setting minimum state value " << stateType.name() << " to " << minValue;
} }
/*! Sets the minimum value for the \l{State} matching the given \a stateName in this thing to value. */ /*! Sets the minimum value for the \l{State} matching the given \a stateName in this thing to \a minValue. */
void Thing::setStateMinValue(const QString &stateName, const QVariant &minValue) void Thing::setStateMinValue(const QString &stateName, const QVariant &minValue)
{ {
StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id(); StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
setStateMinValue(stateTypeId, minValue); setStateMinValue(stateTypeId, minValue);
} }
/*! Sets the maximum value for the \l{State} matching the given \a stateTypeId in this thing to value. */ /*! Sets the maximum value for the \l{State} matching the given \a stateTypeId in this thing to \a maxValue. */
void Thing::setStateMaxValue(const StateTypeId &stateTypeId, const QVariant &maxValue) void Thing::setStateMaxValue(const StateTypeId &stateTypeId, const QVariant &maxValue)
{ {
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId); StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
@ -619,7 +617,7 @@ void Thing::setStateMaxValue(const StateTypeId &stateTypeId, const QVariant &max
qCWarning(dcThing()).nospace() << this << ": Failed setting maximum state value " << stateType.name() << " to " << maxValue; qCWarning(dcThing()).nospace() << this << ": Failed setting maximum state value " << stateType.name() << " to " << maxValue;
} }
/*! Sets the maximum value for the \l{State} matching the given \a stateName in this thing to value. */ /*! Sets the maximum value for the \l{State} matching the given \a stateName in this thing to \a maxValue. */
void Thing::setStateMaxValue(const QString &stateName, const QVariant &maxValue) void Thing::setStateMaxValue(const QString &stateName, const QVariant &maxValue)
{ {
StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id(); StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
@ -697,6 +695,14 @@ void Thing::setStateMinMaxValues(const QString &stateName, const QVariant &minVa
setStateMinMaxValues(stateTypeId, minValue, maxValue); setStateMinMaxValues(stateTypeId, minValue, maxValue);
} }
/*! Sets the possible values for the \l{State} matching the given \a stateName in this thing to \a values. */
void Thing::setStatePossibleValues(const QString &stateName, const QVariantList &values)
{
const StateTypeId stateTypeId = m_thingClass.stateTypes().findByName(stateName).id();
setStatePossibleValues(stateTypeId, values);
}
/*! Sets the possible values for the \l{State} matching the given \a stateTypeId in this thing to \a values. */
void Thing::setStatePossibleValues(const StateTypeId &stateTypeId, const QVariantList &values) void Thing::setStatePossibleValues(const StateTypeId &stateTypeId, const QVariantList &values)
{ {
StateType stateType = m_thingClass.stateTypes().findById(stateTypeId); StateType stateType = m_thingClass.stateTypes().findById(stateTypeId);
@ -704,6 +710,7 @@ void Thing::setStatePossibleValues(const StateTypeId &stateTypeId, const QVarian
qCWarning(dcThing()) << "No such state type" << stateTypeId.toString() << "in" << m_name << "(" + thingClass().name() + ")"; qCWarning(dcThing()) << "No such state type" << stateTypeId.toString() << "in" << m_name << "(" + thingClass().name() + ")";
return; return;
} }
for (int i = 0; i < m_states.count(); ++i) { for (int i = 0; i < m_states.count(); ++i) {
if (m_states.at(i).stateTypeId() == stateTypeId) { if (m_states.at(i).stateTypeId() == stateTypeId) {
if (values == m_states.at(i).possibleValues()) { if (values == m_states.at(i).possibleValues()) {
@ -739,10 +746,10 @@ void Thing::setStatePossibleValues(const StateTypeId &stateTypeId, const QVarian
return; return;
} }
} }
qCWarning(dcThing()).nospace() << this << ": Failed setting maximum state value " << stateType.name() << " to " << values; qCWarning(dcThing()).nospace() << this << ": Failed setting possible state values " << stateType.name() << " to " << values;
Q_ASSERT_X(false, Q_ASSERT_X(false,
m_name.toUtf8(), m_name.toUtf8(),
QString("Failed setting possible state values for %1 to %2").arg(stateType.name()).arg(QString(QJsonDocument::fromVariant(values).toJson())).toUtf8()); QString("Failed setting possible state values for %1 to %2").arg(stateType.name(), QString(QJsonDocument::fromVariant(values).toJson())).toUtf8());
} }
/*! Returns the \l{State} with the given \a stateTypeId of this thing. */ /*! Returns the \l{State} with the given \a stateTypeId of this thing. */

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of nymea. * This file is part of nymea.
* *
@ -32,7 +32,6 @@
#include "types/state.h" #include "types/state.h"
#include "types/param.h" #include "types/param.h"
#include "types/event.h" #include "types/event.h"
#include "types/browseritem.h"
#include <QObject> #include <QObject>
#include <QUuid> #include <QUuid>
@ -145,6 +144,7 @@ public:
Q_INVOKABLE void setStateMinMaxValues(const StateTypeId &stateTypeId, const QVariant &minValue, const QVariant &maxValue); Q_INVOKABLE void setStateMinMaxValues(const StateTypeId &stateTypeId, const QVariant &minValue, const QVariant &maxValue);
Q_INVOKABLE void setStateMinMaxValues(const QString &stateName, const QVariant &minValue, const QVariant &maxValue); Q_INVOKABLE void setStateMinMaxValues(const QString &stateName, const QVariant &minValue, const QVariant &maxValue);
Q_INVOKABLE void setStatePossibleValues(const StateTypeId &stateTypeId, const QVariantList &values); Q_INVOKABLE void setStatePossibleValues(const StateTypeId &stateTypeId, const QVariantList &values);
Q_INVOKABLE void setStatePossibleValues(const QString &stateName, const QVariantList &values);
Q_INVOKABLE State state(const StateTypeId &stateTypeId) const; Q_INVOKABLE State state(const StateTypeId &stateTypeId) const;
Q_INVOKABLE State state(const QString &stateName) const; Q_INVOKABLE State state(const QString &stateName) const;

View File

@ -24,9 +24,10 @@
#include "jsoncontext.h" #include "jsoncontext.h"
JsonContext::JsonContext(const QUuid &clientId, const QLocale &locale): JsonContext::JsonContext(const QUuid &clientId, const QLocale &locale, bool authenticationEnabled):
m_clientId(clientId), m_clientId(clientId),
m_locale(locale) m_locale(locale),
m_authenticationEnabled(authenticationEnabled)
{ {
} }
@ -50,3 +51,13 @@ void JsonContext::setToken(const QByteArray &token)
{ {
m_token = token; m_token = token;
} }
bool JsonContext::authenticationEnabled() const
{
return m_authenticationEnabled;
}
void JsonContext::setAuthenticationEnabled(bool authenticationEnabled)
{
m_authenticationEnabled = authenticationEnabled;
}

View File

@ -31,7 +31,7 @@
class JsonContext class JsonContext
{ {
public: public:
JsonContext(const QUuid &clientId, const QLocale &locale); JsonContext(const QUuid &clientId, const QLocale &locale, bool authenticationEnabled = true);
QUuid clientId() const; QUuid clientId() const;
QLocale locale() const; QLocale locale() const;
@ -39,10 +39,14 @@ public:
QByteArray token() const; QByteArray token() const;
void setToken(const QByteArray &token); void setToken(const QByteArray &token);
bool authenticationEnabled() const;
void setAuthenticationEnabled(bool authenticationEnabled);
private: private:
QUuid m_clientId; QUuid m_clientId;
QLocale m_locale; QLocale m_locale;
QByteArray m_token; QByteArray m_token;
bool m_authenticationEnabled = true;
}; };
#endif // JSONCONTEXT_H #endif // JSONCONTEXT_H

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of nymea. * This file is part of nymea.
* *
@ -24,9 +24,21 @@
#include "platformupdatecontroller.h" #include "platformupdatecontroller.h"
PlatformUpdateController::PlatformUpdateController(QObject *parent) : QObject(parent) PlatformUpdateController::PlatformUpdateController(QObject *parent)
{ : QObject(parent)
{}
/*! Indicates whether the update of this platform is package manager based or entire System updates or none.
On platforms like debian the update is mostly package manager based using apt in the background.
On many products like yocto or mender based systems there are system update images, where the user has no
influcence on individual packages, only to select between entire system images and their updates.
A backend plugin should override this to indicate the type of the system update.
*/
PlatformUpdateController::UpdateType PlatformUpdateController::updateType() const
{
return PlatformUpdateController::UpdateTypeNone;
} }
/*! Whether or not the update management is available. Returns true if the system is ready /*! Whether or not the update management is available. Returns true if the system is ready
@ -78,6 +90,16 @@ bool PlatformUpdateController::updateRunning() const
return false; return false;
} }
/*! Indicates the progress of the update as percentage. Since not all update platforms support this feature,
the value defaults to -1 if not supported or not running.
A backend plugin should override this and return actual update percentage if supported.
*/
int PlatformUpdateController::updateProgress() const
{
return -1;
}
/*! Returns a list of packages availabe in the system. If a backend supports installation of new packages, /*! Returns a list of packages availabe in the system. If a backend supports installation of new packages,
the list of packages may contain not installed packages. Such packages are marked by the list of packages may contain not installed packages. Such packages are marked by
returning an empty \l{Package::installedVersion()}. If the backend supports removal returning an empty \l{Package::installedVersion()}. If the backend supports removal
@ -163,4 +185,3 @@ bool PlatformUpdateController::enableRepository(const QString &repositoryId, boo
Q_UNUSED(enabled) Q_UNUSED(enabled)
return false; return false;
} }

View File

@ -3,7 +3,7 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH * Copyright (C) 2024 - 2026, chargebyte austria GmbH
* *
* This file is part of nymea. * This file is part of nymea.
* *
@ -34,14 +34,24 @@ class PlatformUpdateController : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum UpdateType {
UpdateTypeNone,
UpdateTypeSystem,
UpdateTypePackageManager
};
Q_ENUM(UpdateType)
explicit PlatformUpdateController(QObject *parent = nullptr); explicit PlatformUpdateController(QObject *parent = nullptr);
virtual ~PlatformUpdateController() = default; virtual ~PlatformUpdateController() = default;
virtual PlatformUpdateController::UpdateType updateType() const;
virtual bool updateManagementAvailable() const; virtual bool updateManagementAvailable() const;
virtual bool checkForUpdates(); virtual bool checkForUpdates();
virtual bool busy() const; virtual bool busy() const;
virtual bool updateRunning() const; virtual bool updateRunning() const;
virtual int updateProgress() const;
virtual QList<Package> packages() const; virtual QList<Package> packages() const;
virtual QList<Repository> repositories() const; virtual QList<Repository> repositories() const;
@ -56,6 +66,7 @@ signals:
void availableChanged(); void availableChanged();
void busyChanged(); void busyChanged();
void updateRunningChanged(); void updateRunningChanged();
void updateProgressChanged();
void packageAdded(const Package &pacakge); void packageAdded(const Package &pacakge);
void packageChanged(const Package &package); void packageChanged(const Package &package);
void packageRemoved(const QString &packageId); void packageRemoved(const QString &packageId);

View File

@ -11,10 +11,10 @@ isEmpty(NYMEA_VERSION) {
# define protocol versions # define protocol versions
JSON_PROTOCOL_VERSION_MAJOR=8 JSON_PROTOCOL_VERSION_MAJOR=8
JSON_PROTOCOL_VERSION_MINOR=4 JSON_PROTOCOL_VERSION_MINOR=5
JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}"
LIBNYMEA_API_VERSION_MAJOR=9 LIBNYMEA_API_VERSION_MAJOR=9
LIBNYMEA_API_VERSION_MINOR=0 LIBNYMEA_API_VERSION_MINOR=1
LIBNYMEA_API_VERSION_PATCH=0 LIBNYMEA_API_VERSION_PATCH=0
LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}" LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}"

View File

@ -1,4 +1,4 @@
8.4 8.5
{ {
"enums": { "enums": {
"BasicType": [ "BasicType": [
@ -359,6 +359,11 @@
"UnitLiter", "UnitLiter",
"UnitMicroGrammPerCubicalMeter" "UnitMicroGrammPerCubicalMeter"
], ],
"UpdateType": [
"UpdateTypeNone",
"UpdateTypeSystem",
"UpdateTypePackageManager"
],
"UserError": [ "UserError": [
"UserErrorNoError", "UserErrorNoError",
"UserErrorBackendError", "UserErrorBackendError",
@ -1785,14 +1790,15 @@
} }
}, },
"System.GetCapabilities": { "System.GetCapabilities": {
"description": "Get the list of capabilites on this system. The property \"powerManagement\" indicates whether restarting nymea and rebooting or shutting down is supported on this system. The property \"updateManagement indicates whether system update features are available in this system. The property \"timeManagement\" indicates whether the system time can be configured on this system. Note that GetTime will be available in any case.", "description": "Get the list of capabilites on this system. The property \"powerManagement\" indicates whether restarting nymea and rebooting or shutting down is supported on this system. The property \"updateManagement\" indicates whether system update features are available in this system. The \"updateManagementType\" indicates which kind of update is supported on this platform. The property \"timeManagement\" indicates whether the system time can be configured on this system. Note that GetTime will be available in any case.",
"params": { "params": {
}, },
"permissionScope": "PermissionScopeAdmin", "permissionScope": "PermissionScopeAdmin",
"returns": { "returns": {
"powerManagement": "Bool", "powerManagement": "Bool",
"timeManagement": "Bool", "timeManagement": "Bool",
"updateManagement": "Bool" "updateManagement": "Bool",
"updateManagementType": "$ref:UpdateType"
} }
}, },
"System.GetPackages": { "System.GetPackages": {
@ -1844,12 +1850,13 @@
} }
}, },
"System.GetUpdateStatus": { "System.GetUpdateStatus": {
"description": "Get the current status of the update system. \"busy\" indicates that the system is current busy with an operation regarding updates. This does not necessarily mean an actual update is running. When this is true, update related functions on the client should be marked as busy and no interaction with update components shall be allowed. An example for such a state is when the system queries the server if there are updates available, typically after a call to CheckForUpdates. \"updateRunning\" on the other hand indicates an actual update process is ongoing. The user should be informed about it, the system also might restart at any point while an update is running.", "description": "Get the current status of the update system. \"busy\" indicates that the system is current busy with an operation regarding updates. This does not necessarily mean an actual update is running. When this is true, update related functions on the client should be marked as busy and no interaction with update components shall be allowed. An example for such a state is when the system queries the server if there are updates available, typically after a call to CheckForUpdates. \"updateRunning\" on the other hand indicates an actual update process is ongoing. The user should be informed about it, the system also might restart at any point while an update is running. The \"updateProgress\" property is optional, if the backend supports it, a progress >= 0 indicated the update progress in percentage.",
"params": { "params": {
}, },
"permissionScope": "PermissionScopeAdmin", "permissionScope": "PermissionScopeAdmin",
"returns": { "returns": {
"busy": "Bool", "busy": "Bool",
"o:updateProgress": "Int",
"updateRunning": "Bool" "updateRunning": "Bool"
} }
}, },
@ -2684,7 +2691,8 @@
"description": "Emitted whenever the system capabilities change.", "description": "Emitted whenever the system capabilities change.",
"params": { "params": {
"powerManagement": "Bool", "powerManagement": "Bool",
"updateManagement": "Bool" "updateManagement": "Bool",
"updateManagementType": "$ref:UpdateType"
} }
}, },
"System.PackageAdded": { "System.PackageAdded": {
@ -2736,6 +2744,7 @@
"description": "Emitted whenever the update status changes.", "description": "Emitted whenever the update status changes.",
"params": { "params": {
"busy": "Bool", "busy": "Bool",
"o:updateProgress": "Int",
"updateRunning": "Bool" "updateRunning": "Bool"
} }
}, },