Merge PR #190: System controller

This commit is contained in:
Jenkins 2019-05-18 02:38:40 +02:00
commit 80e3559578
34 changed files with 1911 additions and 24 deletions

View File

@ -284,7 +284,6 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap &params)
webServerConfigurations()->clear();
foreach (const QVariant &webServerVariant, params.value("params").toMap().value("webServerConfigurations").toList()) {
QVariantMap webServerConfigMap = webServerVariant.toMap();
qDebug() << "**********+ web config" << webServerConfigMap;
WebServerConfiguration* config = new WebServerConfiguration(webServerConfigMap.value("id").toString(), QHostAddress(webServerConfigMap.value("address").toString()), webServerConfigMap.value("port").toInt(), webServerConfigMap.value("authenticationEnabled").toBool(), webServerConfigMap.value("sslEnabled").toBool());
config->setPublicFolder(webServerConfigMap.value("publicFolder").toString());
m_webServerConfigurations->addConfiguration(config);
@ -293,7 +292,7 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap &params)
void NymeaConfiguration::getAvailableLanguagesResponse(const QVariantMap &params)
{
qDebug() << "available languages" << params;
// qDebug() << "available languages" << params;
m_availableLanguages = params.value("params").toMap().value("languages").toStringList();
emit availableLanguagesChanged();
}

View File

@ -108,7 +108,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
if (!host) {
host = new NymeaHost(m_nymeaHosts);
host->setUuid(uuid);
qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
// qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
m_nymeaHosts->addHost(host);
}
host->setName(serverName);
@ -124,13 +124,13 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
url.setPort(entry.port());
Connection *connection = host->connections()->find(url);
if (!connection) {
qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString();
// qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString();
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
connection = new Connection(url, Connection::BearerTypeLan, sslEnabled, displayName);
connection->setOnline(true);
host->connections()->addConnection(connection);
} else {
qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
// qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
connection->setOnline(true);
}
}
@ -183,7 +183,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry)
return;
}
qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString();
// qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString();
connection->setOnline(false);
}
#endif

View File

@ -25,6 +25,7 @@
#include "tagsmanager.h"
#include "configuration/nymeaconfiguration.h"
#include "connection/awsclient.h"
#include "system/systemcontroller.h"
#include "connection/tcpsockettransport.h"
#include "connection/websockettransport.h"
@ -39,7 +40,8 @@ Engine::Engine(QObject *parent) :
m_ruleManager(new RuleManager(m_jsonRpcClient, this)),
m_logManager(new LogManager(m_jsonRpcClient, this)),
m_tagsManager(new TagsManager(m_jsonRpcClient, this)),
m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this))
m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)),
m_systemController(new SystemController(m_jsonRpcClient, this))
{
m_connection->registerTransport(new TcpSocketTransportFactory());
m_connection->registerTransport(new WebsocketTransportFactory());
@ -97,6 +99,11 @@ NymeaConfiguration *Engine::nymeaConfiguration() const
return m_nymeaConfiguration;
}
SystemController *Engine::systemController() const
{
return m_systemController;
}
void Engine::deployCertificate()
{
if (!m_jsonRpcClient->connected()) {
@ -129,6 +136,7 @@ void Engine::onConnectedChanged()
m_deviceManager->init();
m_ruleManager->init();
m_nymeaConfiguration->init();
m_systemController->init();
}
}
}

View File

@ -32,6 +32,7 @@ class RuleManager;
class LogManager;
class TagsManager;
class NymeaConfiguration;
class SystemController;
class Engine : public QObject
{
@ -42,6 +43,7 @@ class Engine : public QObject
Q_PROPERTY(TagsManager* tagsManager READ tagsManager CONSTANT)
Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient CONSTANT)
Q_PROPERTY(NymeaConfiguration* nymeaConfiguration READ nymeaConfiguration CONSTANT)
Q_PROPERTY(SystemController* systemController READ systemController CONSTANT)
public:
explicit Engine(QObject *parent = nullptr);
@ -56,6 +58,7 @@ public:
JsonRpcClient *jsonRpcClient() const;
LogManager *logManager() const;
NymeaConfiguration *nymeaConfiguration() const;
SystemController *systemController() const;
Q_INVOKABLE void deployCertificate();
@ -67,6 +70,7 @@ private:
LogManager *m_logManager;
TagsManager *m_tagsManager;
NymeaConfiguration *m_nymeaConfiguration;
SystemController *m_systemController;
private slots:
void onConnectedChanged();

View File

@ -133,7 +133,7 @@ void JsonRpcClient::notificationReceived(const QVariantMap &data)
void JsonRpcClient::isCloudConnectedReply(const QVariantMap &data)
{
qDebug() << "Cloud is connected" << data;
// qDebug() << "Cloud is connected" << data;
QMetaEnum connectionStateEnum = QMetaEnum::fromType<CloudConnectionState>();
m_cloudConnectionState = static_cast<CloudConnectionState>(connectionStateEnum.keyToValue(data.value("params").toMap().value("connectionState").toByteArray().data()));
emit cloudConnectionStateChanged();

View File

@ -56,6 +56,12 @@
#include "ruletemplates/ruleactionparamtemplate.h"
#include "connection/awsclient.h"
#include "models/devicemodel.h"
#include "system/systemcontroller.h"
#include "types/package.h"
#include "types/packages.h"
#include "types/repository.h"
#include "types/repositories.h"
#include "models/packagesfiltermodel.h"
#include <QtQml/qqml.h>
@ -199,6 +205,13 @@ void registerQmlTypes() {
qmlRegisterUncreatableType<RuleActionTemplate>(uri, 1, 0, "RuleActionTemplate", "Get it from RuleActionTemplates");
qmlRegisterUncreatableType<RuleActionParamTemplates>(uri, 1, 0, "RuleActionParamTemplates", "Get it from RuleActionTemplate");
qmlRegisterUncreatableType<RuleActionParamTemplate>(uri, 1, 0, "RuleActionParamTemplate", "Get it from RuleActionParamTemplates");
qmlRegisterUncreatableType<SystemController>(uri, 1, 0, "SystemController", "Get it from Engine");
qmlRegisterUncreatableType<Packages>(uri, 1, 0, "Packages", "Get it from SystemController");
qmlRegisterUncreatableType<Package>(uri, 1, 0, "Package", "Get it from Packages");
qmlRegisterUncreatableType<Repositories>(uri, 1, 0, "Repositories", "Get it from SystemController");
qmlRegisterUncreatableType<Repository>(uri, 1, 0, "Repository", "Get it from Repositories");
qmlRegisterType<PackagesFilterModel>(uri, 1, 0, "PackagesFilterModel");
}
#endif // LIBNYMEAAPPCORE_H

View File

@ -46,6 +46,7 @@ SOURCES += \
deviceclasses.cpp \
deviceclassesproxy.cpp \
devicediscovery.cpp \
models/packagesfiltermodel.cpp \
vendorsproxy.cpp \
pluginsproxy.cpp \
interfacesmodel.cpp \
@ -81,7 +82,8 @@ SOURCES += \
configuration/nymeaconfiguration.cpp \
configuration/mqttpolicy.cpp \
configuration/mqttpolicies.cpp \
models/devicemodel.cpp
models/devicemodel.cpp \
system/systemcontroller.cpp
HEADERS += \
engine.h \
@ -107,6 +109,7 @@ HEADERS += \
deviceclasses.h \
deviceclassesproxy.h \
devicediscovery.h \
models/packagesfiltermodel.h \
vendorsproxy.h \
pluginsproxy.h \
interfacesmodel.h \
@ -142,4 +145,10 @@ HEADERS += \
configuration/nymeaconfiguration.h \
configuration/mqttpolicy.h \
configuration/mqttpolicies.h \
models/devicemodel.h
models/devicemodel.h \
system/systemcontroller.h
unix {
target.path = /usr/lib
INSTALLS += target
}

View File

@ -0,0 +1,55 @@
#include "packagesfiltermodel.h"
#include "types/package.h"
PackagesFilterModel::PackagesFilterModel(QObject *parent): QSortFilterProxyModel(parent)
{
setSortRole(Packages::RoleDisplayName);
sort(0);
}
Packages *PackagesFilterModel::packages() const
{
return m_packages;
}
void PackagesFilterModel::setPackages(Packages *packages)
{
if (m_packages != packages) {
m_packages = packages;
setSourceModel(packages);
connect(packages, &Packages::countChanged, this, &PackagesFilterModel::countChanged);
emit packagesChanged();
emit countChanged();
invalidate();
}
}
bool PackagesFilterModel::updatesOnly() const
{
return m_updatesOnly;
}
void PackagesFilterModel::setUpdatesOnly(bool updatesOnly)
{
if (m_updatesOnly != updatesOnly) {
m_updatesOnly = updatesOnly;
emit updatesOnlyChanged();
invalidateFilter();
emit countChanged();
}
}
Package *PackagesFilterModel::get(int index) const
{
return m_packages->get(mapToSource(this->index(index, 0)).row());
}
bool PackagesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (m_updatesOnly) {
if (!m_packages->get(source_row)->updateAvailable()) {
return false;
}
}
return true;
}

View File

@ -0,0 +1,39 @@
#ifndef PACKAGESFILTERMODEL_H
#define PACKAGESFILTERMODEL_H
#include <QSortFilterProxyModel>
#include "types/packages.h"
class PackagesFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(Packages* packages READ packages WRITE setPackages NOTIFY packagesChanged)
Q_PROPERTY(bool updatesOnly READ updatesOnly WRITE setUpdatesOnly NOTIFY updatesOnlyChanged)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
explicit PackagesFilterModel(QObject *parent = nullptr);
Packages* packages() const;
void setPackages(Packages *packages);
bool updatesOnly() const;
void setUpdatesOnly(bool updatesOnly);
Q_INVOKABLE Package* get(int index) const;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
signals:
void countChanged();
void packagesChanged();
void updatesOnlyChanged();
private:
Packages *m_packages;
bool m_updatesOnly = false;
};
#endif // PACKAGESFILTERMODEL_H

View File

@ -0,0 +1,205 @@
#include "systemcontroller.h"
#include "types/package.h"
#include "types/repository.h"
#include "types/packages.h"
#include "types/repositories.h"
SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent):
JsonHandler(parent),
m_jsonRpcClient(jsonRpcClient)
{
m_jsonRpcClient->registerNotificationHandler(this, "notificationReceived");
m_packages = new Packages(this);
m_repositories = new Repositories(this);
}
void SystemController::init()
{
m_packages->clear();
m_repositories->clear();
if (m_jsonRpcClient->ensureServerVersion("2.0")) {
m_jsonRpcClient->sendCommand("System.GetCapabilities", this, "getCapabilitiesResponse");
} else {
m_powerManagementAvailable = false;
}
}
QString SystemController::nameSpace() const
{
return "System";
}
bool SystemController::powerManagementAvailable() const
{
return m_powerManagementAvailable;
}
bool SystemController::updateManagementAvailable() const
{
return m_updateManagementAvailable;
}
void SystemController::reboot()
{
m_jsonRpcClient->sendCommand("System.Reboot");
}
void SystemController::shutdown()
{
m_jsonRpcClient->sendCommand("System.Shutdown");
}
bool SystemController::updateRunning() const
{
return m_updateRunning;
}
Packages *SystemController::packages() const
{
return m_packages;
}
void SystemController::updatePackages(const QString packageId)
{
QVariantMap params;
if (!packageId.isEmpty()) {
params.insert("packageIds", QStringList() << packageId);
}
m_jsonRpcClient->sendCommand("System.UpdatePackages", params);
}
void SystemController::removePackages(const QString packageId)
{
QVariantMap params;
if (!packageId.isEmpty()) {
params.insert("packageIds", QStringList() << packageId);
}
m_jsonRpcClient->sendCommand("System.RemovePackages", params, this, "removePackageResponse");
}
Repositories *SystemController::repositories() const
{
return m_repositories;
}
void SystemController::enableRepository(const QString &id, bool enabled)
{
QVariantMap params;
params.insert("repositoryId", id);
params.insert("enabled", enabled);
m_jsonRpcClient->sendCommand("System.EnableRepository", params);
}
void SystemController::getCapabilitiesResponse(const QVariantMap &data)
{
qDebug() << "capabilities received" << data;
m_powerManagementAvailable = data.value("params").toMap().value("powerManagement").toBool();
emit powerManagementAvailableChanged();
m_updateManagementAvailable = data.value("params").toMap().value("updateManagement").toBool();
emit updateManagementAvailableChanged();
if (m_updateManagementAvailable) {
m_jsonRpcClient->sendCommand("System.GetUpdateStatus", this, "getUpdateStatusResponse");
m_jsonRpcClient->sendCommand("System.GetPackages", this, "getPackagesResponse");
m_jsonRpcClient->sendCommand("System.GetRepositories", this, "getRepositoriesResponse");
}
}
void SystemController::getUpdateStatusResponse(const QVariantMap &data)
{
m_updateRunning = data.value("params").toMap().value("updateRunning").toBool();
emit updateRunningChanged();
}
void SystemController::getPackagesResponse(const QVariantMap &data)
{
foreach (const QVariant &packageVariant, data.value("params").toMap().value("packages").toList()) {
QString id = packageVariant.toMap().value("id").toString();
QString displayName = packageVariant.toMap().value("displayName").toString();
Package *p = new Package(id, displayName);
p->setInstalledVersion(packageVariant.toMap().value("installedVersion").toString());
p->setCandidateVersion(packageVariant.toMap().value("candidateVersion").toString());
p->setChangelog(packageVariant.toMap().value("changelog").toString());
p->setUpdateAvailable(packageVariant.toMap().value("updateAvailable").toBool());
p->setRollbackAvailable(packageVariant.toMap().value("rollbackAvailable").toBool());
p->setCanRemove(packageVariant.toMap().value("canRemove").toBool());
m_packages->addPackage(p);
}
}
void SystemController::getRepositoriesResponse(const QVariantMap &data)
{
qDebug() << "******** Repos" << data;
foreach (const QVariant &repoVariant, data.value("params").toMap().value("repositories").toList()) {
QString id = repoVariant.toMap().value("id").toString();
QString displayName = repoVariant.toMap().value("displayName").toString();
Repository *repo = new Repository(id, displayName);
repo->setEnabled(repoVariant.toMap().value("enabled").toBool());
m_repositories->addRepository(repo);
}
}
void SystemController::removePackageResponse(const QVariantMap &params)
{
qDebug() << "Remove result" << params;
}
void SystemController::notificationReceived(const QVariantMap &data)
{
qDebug() << "System Notification" << data.value("notification");
QString notification = data.value("notification").toString();
if (notification == "System.UpdateStatusChanged") {
m_updateRunning = data.value("params").toMap().value("updateRunning").toBool();
emit updateRunningChanged();
} else if (notification == "System.PackageAdded") {
QVariantMap packageMap = data.value("params").toMap().value("package").toMap();
QString id = packageMap.value("id").toString();
QString displayName = packageMap.value("displayName").toString();
Package *p = new Package(id, displayName);
p->setInstalledVersion(packageMap.value("installedVersion").toString());
p->setCandidateVersion(packageMap.value("candidateVersion").toString());
p->setChangelog(packageMap.value("changelog").toString());
p->setUpdateAvailable(packageMap.value("updateAvailable").toBool());
p->setRollbackAvailable(packageMap.value("rollbackAvailable").toBool());
p->setCanRemove(packageMap.value("canRemove").toBool());
m_packages->addPackage(p);
} else if (notification == "System.PackageChanged") {
QVariantMap packageMap = data.value("params").toMap().value("package").toMap();
QString id = packageMap.value("id").toString();
Package *p = m_packages->getPackage(id);
if (!p) {
qWarning() << "Received a package update notification for a package we don't know";
return;
}
p->setInstalledVersion(packageMap.value("installedVersion").toString());
p->setCandidateVersion(packageMap.value("candidateVersion").toString());
p->setChangelog(packageMap.value("changelog").toString());
p->setUpdateAvailable(packageMap.value("updateAvailable").toBool());
p->setRollbackAvailable(packageMap.value("rollbackAvailable").toBool());
p->setCanRemove(packageMap.value("canRemove").toBool());
} else if (notification == "System.PackageRemoved") {
QString packageId = data.value("params").toMap().value("packageId").toString();
m_packages->removePackage(packageId);
} else if (notification == "System.RepositoryAdded") {
QVariantMap repoMap = data.value("params").toMap().value("repository").toMap();
QString id = repoMap.value("id").toString();
QString displayName = repoMap.value("displayName").toString();
Repository *repo = new Repository(id, displayName);
repo->setEnabled(repoMap.value("enabled").toBool());
m_repositories->addRepository(repo);
} else if (notification == "System.RepositoryChanged") {
QVariantMap repoMap = data.value("params").toMap().value("repository").toMap();
QString id = repoMap.value("id").toString();
Repository *repo = m_repositories->getRepository(id);
if (!repo) {
qWarning() << "Received a repository update notification for a repository we don't know";
return;
}
repo->setEnabled(repoMap.value("enabled").toBool());
} else if (notification == "System.RepositoryRemoved") {
QString repositoryId = data.value("params").toMap().value("repositoryId").toString();
m_repositories->removeRepository(repositoryId);
}
}

View File

@ -0,0 +1,69 @@
#ifndef SYSTEMCONTROLLER_H
#define SYSTEMCONTROLLER_H
#include <QObject>
#include "jsonrpc/jsonrpcclient.h"
class Repositories;
class Packages;
class SystemController : public JsonHandler
{
Q_OBJECT
Q_PROPERTY(bool powerManagementAvailable READ powerManagementAvailable NOTIFY powerManagementAvailableChanged)
// Whether the update mechanism is available in the connected core
Q_PROPERTY(bool updateManagementAvailable READ updateManagementAvailable NOTIFY updateManagementAvailableChanged)
Q_PROPERTY(bool updateRunning READ updateRunning NOTIFY updateRunningChanged)
Q_PROPERTY(Packages* packages READ packages CONSTANT)
Q_PROPERTY(Repositories* repositories READ repositories CONSTANT)
public:
explicit SystemController(JsonRpcClient *jsonRpcClient, QObject *parent = nullptr);
void init();
QString nameSpace() const override;
bool powerManagementAvailable() const;
bool updateManagementAvailable() const;
Q_INVOKABLE void reboot();
Q_INVOKABLE void shutdown();
bool updateRunning() const;
Packages* packages() const;
Q_INVOKABLE void updatePackages(const QString packageId = QString());
Q_INVOKABLE void removePackages(const QString packageId = QString());
Repositories* repositories() const;
Q_INVOKABLE void enableRepository(const QString &id, bool enabled);
signals:
void powerManagementAvailableChanged();
void updateManagementAvailableChanged();
void updateRunningChanged();
private slots:
void getCapabilitiesResponse(const QVariantMap &data);
void getUpdateStatusResponse(const QVariantMap &data);
void getPackagesResponse(const QVariantMap &data);
void getRepositoriesResponse(const QVariantMap &data);
void removePackageResponse(const QVariantMap &params);
void notificationReceived(const QVariantMap &data);
private:
JsonRpcClient *m_jsonRpcClient = nullptr;
bool m_powerManagementAvailable = false;
bool m_updateManagementAvailable = false;
bool m_updateRunning = false;
Packages *m_packages = nullptr;
Repositories *m_repositories = nullptr;
};
#endif // SYSTEMCONTROLLER_H

View File

@ -8,6 +8,10 @@ QT -= gui
QT += network
HEADERS += \
types/package.h \
types/packages.h \
types/repositories.h \
types/repository.h \
types/types.h \
types/vendor.h \
types/vendors.h \
@ -54,6 +58,10 @@ HEADERS += \
types/tags.h
SOURCES += \
types/package.cpp \
types/packages.cpp \
types/repositories.cpp \
types/repository.cpp \
types/vendor.cpp \
types/vendors.cpp \
types/deviceclass.cpp \

View File

@ -0,0 +1,97 @@
#include "package.h"
Package::Package(const QString &id, const QString &displayName, QObject *parent):
QObject(parent),
m_id(id),
m_displayName(displayName)
{
}
QString Package::id() const
{
return m_id;
}
QString Package::displayName() const
{
return m_displayName;
}
QString Package::installedVersion() const
{
return m_installedVersion;
}
void Package::setInstalledVersion(const QString &installedVersion)
{
if (m_installedVersion != installedVersion) {
m_installedVersion = installedVersion;
emit installedVersionChanged();
}
}
QString Package::candidateVersion() const
{
return m_candidateVersion;
}
void Package::setCandidateVersion(const QString &candidateVersion)
{
if (m_candidateVersion != candidateVersion) {
m_candidateVersion = candidateVersion;
emit candidateVersionChanged();
}
}
QString Package::changelog() const
{
return m_changelog;
}
void Package::setChangelog(const QString &changelog)
{
if (m_changelog != changelog) {
m_changelog = changelog;
emit changelogChanged();
}
}
bool Package::updateAvailable() const
{
return m_updateAvailable;
}
void Package::setUpdateAvailable(bool updateAvailable)
{
if (m_updateAvailable != updateAvailable) {
m_updateAvailable = updateAvailable;
emit updateAvailableChanged();
}
}
bool Package::rollbackAvailable() const
{
return m_rollbackAvailable;
}
void Package::setRollbackAvailable(bool rollbackAvailable)
{
if (m_rollbackAvailable != rollbackAvailable) {
m_rollbackAvailable = rollbackAvailable;
emit rollbackAvailableChanged();
}
}
bool Package::canRemove() const
{
return m_canRemove;
}
void Package::setCanRemove(bool canRemove)
{
if (m_canRemove != canRemove) {
m_canRemove = canRemove;
emit canRemoveChanged();
}
}

View File

@ -0,0 +1,61 @@
#ifndef PACKAGE_H
#define PACKAGE_H
#include <QObject>
class Package : public QObject
{
Q_OBJECT
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
Q_PROPERTY(QString installedVersion READ installedVersion NOTIFY installedVersionChanged)
Q_PROPERTY(QString candidateVersion READ candidateVersion NOTIFY candidateVersionChanged)
Q_PROPERTY(QString changelog READ changelog NOTIFY changelogChanged)
Q_PROPERTY(bool updateAvailable READ updateAvailable NOTIFY updateAvailableChanged)
Q_PROPERTY(bool rollbackAvailable READ rollbackAvailable NOTIFY rollbackAvailableChanged)
Q_PROPERTY(bool canRemove READ canRemove NOTIFY canRemoveChanged)
public:
explicit Package(const QString &id, const QString &displayName, QObject *parent = nullptr);
QString id() const;
QString displayName() const;
QString installedVersion() const;
void setInstalledVersion(const QString &installedVersion);
QString candidateVersion() const;
void setCandidateVersion(const QString &candidateVersion);
QString changelog() const;
void setChangelog(const QString &changelog);
bool updateAvailable() const;
void setUpdateAvailable(bool updateAvailable);
bool rollbackAvailable() const;
void setRollbackAvailable(bool rollbackAvailable);
bool canRemove() const;
void setCanRemove(bool canRemove);
signals:
void installedVersionChanged();
void candidateVersionChanged();
void changelogChanged();
void updateAvailableChanged();
void rollbackAvailableChanged();
void canRemoveChanged();
private:
QString m_id;
QString m_displayName;
QString m_installedVersion;
QString m_candidateVersion;
QString m_changelog;
bool m_updateAvailable = false;
bool m_rollbackAvailable = false;
bool m_canRemove = false;
};
#endif // PACKAGE_H

View File

@ -0,0 +1,122 @@
#include "packages.h"
#include "package.h"
Packages::Packages(QObject *parent) : QAbstractListModel(parent)
{
}
int Packages::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant Packages::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleId:
return m_list.at(index.row())->id();
case RoleDisplayName:
return m_list.at(index.row())->displayName();
case RoleInstalledVersion:
return m_list.at(index.row())->installedVersion();
case RoleCandidateVersion:
return m_list.at(index.row())->candidateVersion();
case RoleChangelog:
return m_list.at(index.row())->changelog();
case RoleUpdateAvailable:
return m_list.at(index.row())->updateAvailable();
case RoleRollbackAvailable:
return m_list.at(index.row())->rollbackAvailable();
}
return QVariant();
}
QHash<int, QByteArray> Packages::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleId, "id");
roles.insert(RoleDisplayName, "displayName");
roles.insert(RoleInstalledVersion, "installedVersion");
roles.insert(RoleCandidateVersion, "candidateVersion");
roles.insert(RoleChangelog, "changelog");
roles.insert(RoleUpdateAvailable, "updateAvailable");
roles.insert(RoleRollbackAvailable, "rollbackAvailable");
return roles;
}
void Packages::addPackage(Package *package)
{
package->setParent(this);
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
m_list.append(package);
connect(package, &Package::installedVersionChanged, this, [this, package](){
emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleInstalledVersion});
emit countChanged();
});
connect(package, &Package::candidateVersionChanged, this, [this, package](){
emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleCandidateVersion});
emit countChanged();
});
connect(package, &Package::changelogChanged, this, [this, package](){
emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleChangelog});
emit countChanged();
});
connect(package, &Package::updateAvailableChanged, this, [this, package](){
emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleUpdateAvailable});
emit countChanged();
});
connect(package, &Package::rollbackAvailableChanged, this, [this, package](){
emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleRollbackAvailable});
emit countChanged();
});
endInsertRows();
emit countChanged();
}
void Packages::removePackage(const QString &packageId)
{
int idx = -1;
for (int i = 0; i < m_list.count(); i++) {
if (m_list.at(i)->id() == packageId) {
idx = i;
break;
}
}
if (idx < 0) {
return;
}
beginRemoveRows(QModelIndex(), idx, idx);
m_list.takeAt(idx)->deleteLater();
endRemoveRows();
emit countChanged();
}
Package *Packages::get(int index) const
{
if (index >= 0 && index < m_list.count()) {
return m_list.at(index);
}
return nullptr;
}
Package *Packages::getPackage(const QString &packageId)
{
foreach (Package *p, m_list) {
if (p->id() == packageId) {
return p;
}
}
return nullptr;
}
void Packages::clear()
{
beginResetModel();
qDeleteAll(m_list);
m_list.clear();
endResetModel();
emit countChanged();
}

View File

@ -0,0 +1,45 @@
#ifndef PACKAGES_H
#define PACKAGES_H
#include <QAbstractListModel>
class Package;
class Packages: public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RoleId,
RoleDisplayName,
RoleInstalledVersion,
RoleCandidateVersion,
RoleChangelog,
RoleUpdateAvailable,
RoleRollbackAvailable
};
Q_ENUM(Roles)
explicit Packages(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
void addPackage(Package *package);
void removePackage(const QString &packageId);
Q_INVOKABLE Package* get(int index) const;
Q_INVOKABLE Package* getPackage(const QString &packageId);
void clear();
signals:
void countChanged();
private:
QList<Package*> m_list;
};
#endif // PACKAGES_H

View File

@ -0,0 +1,94 @@
#include "repositories.h"
#include "repository.h"
Repositories::Repositories(QObject *parent): QAbstractListModel(parent)
{
}
int Repositories::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant Repositories::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleId:
return m_list.at(index.row())->id();
case RoleDisplayName:
return m_list.at(index.row())->displayName();
case RoleEnabled:
return m_list.at(index.row())->enabled();
}
return QVariant();
}
QHash<int, QByteArray> Repositories::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleId, "id");
roles.insert(RoleDisplayName, "displayName");
roles.insert(RoleEnabled, "enabled");
return roles;
}
Repository *Repositories::get(int index) const
{
if (index >= 0 && index < m_list.count()) {
return m_list.at(index);
}
return nullptr;
}
Repository *Repositories::getRepository(const QString &id) const
{
foreach (Repository *repo, m_list) {
if (repo->id() == id) {
return repo;
}
}
return nullptr;
}
void Repositories::addRepository(Repository *repository)
{
repository->setParent(this);
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
m_list.append(repository);
connect(repository, &Repository::enabledChanged, this, [this, repository](){
QModelIndex idx = index(m_list.indexOf(repository));
emit dataChanged(idx, idx, {RoleEnabled});
});
endInsertRows();
emit countChanged();
}
void Repositories::removeRepository(const QString &repositoryId)
{
int idx = -1;
for (int i = 0; i < m_list.count(); i++) {
if (m_list.at(i)->id() == repositoryId) {
idx = i;
break;
}
}
if (idx < 0) {
return;
}
beginRemoveRows(QModelIndex(), idx, idx);
m_list.takeAt(idx)->deleteLater();
endRemoveRows();
emit countChanged();
}
void Repositories::clear()
{
beginResetModel();
qDeleteAll(m_list);
m_list.clear();
endResetModel();
emit countChanged();
}

View File

@ -0,0 +1,40 @@
#ifndef REPOSITORIES_H
#define REPOSITORIES_H
#include <QAbstractListModel>
class Repository;
class Repositories : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RoleId,
RoleDisplayName,
RoleEnabled
};
Q_ENUM(Roles)
explicit Repositories(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE Repository* get(int index) const;
Q_INVOKABLE Repository* getRepository(const QString &id) const;
void addRepository(Repository* repository);
void removeRepository(const QString &repositoryId);
void clear();
signals:
void countChanged();
private:
QList<Repository*> m_list;
};
#endif // REPOSITORIES_H

View File

@ -0,0 +1,32 @@
#include "repository.h"
Repository::Repository(const QString &id, const QString &displayName, QObject *parent):
QObject(parent),
m_id(id),
m_displayName(displayName)
{
}
QString Repository::id() const
{
return m_id;
}
QString Repository::displayName() const
{
return m_displayName;
}
bool Repository::enabled() const
{
return m_enabled;
}
void Repository::setEnabled(bool enabled)
{
if (m_enabled != enabled) {
m_enabled = enabled;
emit enabledChanged();
}
}

View File

@ -0,0 +1,31 @@
#ifndef REPOSITORY_H
#define REPOSITORY_H
#include <QObject>
class Repository : public QObject
{
Q_OBJECT
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
public:
explicit Repository(const QString &id, const QString &displayName, QObject *parent = nullptr);
QString id() const;
QString displayName() const;
bool enabled() const;
void setEnabled(bool enabled);
signals:
void enabledChanged();
private:
QString m_id;
QString m_displayName;
bool m_enabled = false;
};
#endif // REPOSITORY_H

View File

@ -177,5 +177,9 @@
<file>ui/images/network-wifi-offline.svg</file>
<file>ui/images/network-wired-offline.svg</file>
<file>ui/images/preferences-look-and-feel.svg</file>
<file>ui/images/sensors/closable.svg</file>
<file>ui/images/lock-closed.svg</file>
<file>ui/images/lock-open.svg</file>
<file>ui/images/system-update.svg</file>
</qresource>
</RCC>

View File

@ -180,5 +180,7 @@
<file>ui/appsettings/LookAndFeelSettingsPage.qml</file>
<file>ui/appsettings/AppLogPage.qml</file>
<file>ui/magic/SelectStatePage.qml</file>
<file>ui/system/SystemUpdatePage.qml</file>
<file>ui/components/UpdateRunningOverlay.qml</file>
</qresource>
</RCC>

View File

@ -170,6 +170,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/sensors/light.svg")
case "presencesensor":
return Qt.resolvedUrl("images/sensors/presence.svg")
case "closablesensor":
return Qt.resolvedUrl("images/sensors/closable.svg")
case "media":
case "mediacontroller":
return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg")
@ -210,7 +212,7 @@ ApplicationWindow {
case "fingerprintreader":
return Qt.resolvedUrl("images/fingerprint.svg")
case "accesscontrol":
return Qt.resolvedUrl("images/network-secure.svg");
return Qt.resolvedUrl("images/lock-closed.svg");
case "smartmeter":
case "smartmeterconsumer":
case "smartmeterproducer":
@ -244,6 +246,7 @@ ApplicationWindow {
"co2sensor": "turquoise",
"daylightsensor": "gold",
"presencesensor": "darkblue",
"closablesensor": "green",
"smartmeterproducer": "lightgreen",
"smartmeterconsumer": "orange",
"extendedsmartmeterproducer": "blue",

View File

@ -155,6 +155,23 @@ Page {
}
}
Pane {
Layout.fillWidth: true
Material.elevation: layout.isGrid ? 1 : 0
visible: engine.jsonRpcClient.ensureServerVersion("2.1")
padding: 0
MeaListItemDelegate {
width: parent.width
iconName: "../images/system-update.svg"
text: qsTr("System update")
subText: qsTr("Update your %1:core system").arg(app.systemName)
prominentSubText: false
wrapTexts: false
onClicked: pageStack.push(Qt.resolvedUrl("system/SystemUpdatePage.qml"))
}
}
Pane {
Layout.fillWidth: true
Material.elevation: layout.isGrid ? 1 : 0

View File

@ -0,0 +1,44 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
Rectangle {
anchors.fill: parent
color: "#99000000"
visible: shown
property bool shown: false
// Event eater
MouseArea {
anchors.fill: parent
}
ColumnLayout {
anchors.centerIn: parent
width: parent.width
ColorIcon {
height: app.iconSize * 3
width: height
Layout.alignment: Qt.AlignHCenter
name: Qt.resolvedUrl("../images/system-update.svg")
color: app.accentColor
PropertyAnimation on rotation {
from: 0; to: 360;
duration: 2000
loops: Animation.Inifinite
onStopped: start(); // No clue why loops won't work
}
}
Label {
Layout.fillWidth: true
Layout.margins: app.margins * 2
text: qsTr("An update operation is currently running.\nPlease wait for it to complete.")
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: app.largeFont
color: "white"
}
}
}

View File

@ -84,6 +84,7 @@ DeviceListPageBase {
ListElement { interfaceName: "co2sensor"; stateName: "co2" }
ListElement { interfaceName: "daylightsensor"; stateName: "daylight" }
ListElement { interfaceName: "presencesensor"; stateName: "isPresent" }
ListElement { interfaceName: "closablesensor"; stateName: "closed" }
ListElement { interfaceName: "heating"; stateName: "power" }
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
}
@ -100,24 +101,45 @@ DeviceListPageBase {
Layout.preferredHeight: app.iconSize * .8
Layout.preferredWidth: height
Layout.alignment: Qt.AlignVCenter
color: app.interfaceToColor(model.interfaceName)
name: app.interfaceToIcon(model.interfaceName)
color: {
switch (model.interfaceName) {
case "closablesensor":
return sensorValueDelegate.stateValue.value === true ? "green" : "red";
default:
return app.interfaceToColor(model.interfaceName)
}
}
name: {
switch (model.interfaceName) {
case "closablesensor":
return sensorValueDelegate.stateValue.value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg");
default:
return app.interfaceToIcon(model.interfaceName)
}
}
}
Label {
Layout.fillWidth: true
text: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool"
? sensorValueDelegate.stateType.displayName
: sensorValueDelegate.stateValue
? "%1 %2".arg(Math.round(sensorValueDelegate.stateValue.value * 100) / 100).arg(sensorValueDelegate.stateType.unitString)
: ""
text: {
switch (model.interfaceName) {
case "closablesensor":
return sensorValueDelegate.stateValue.value === true ? qsTr("is closed") : qsTr("is open");
default:
return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool"
? sensorValueDelegate.stateType.displayName
: sensorValueDelegate.stateValue
? "%1 %2".arg(Math.round(sensorValueDelegate.stateValue.value * 100) / 100).arg(sensorValueDelegate.stateType.unitString)
: ""
}
}
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
font.pixelSize: app.smallFont
}
Led {
id: led
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() == "bool"
visible: ["presencesensor", "daylightsensor"].indexOf(model.interfaceName) >= 0
state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off"
}
Item {

View File

@ -11,7 +11,7 @@ ListView {
interactive: contentHeight > height
model: ListModel {
Component.onCompleted: {
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor"]
var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
for (var i = 0; i < supportedInterfaces.length; i++) {
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
append({name: supportedInterfaces[i]});
@ -38,7 +38,8 @@ ListView {
"noisesensor": "noise",
"co2sensor": "co2",
"presencesensor": "isPresent",
"daylightsensor": "daylight"
"daylightsensor": "daylight",
"closablesensor": "closed"
}
}
@ -72,8 +73,22 @@ ListView {
anchors.centerIn: parent
height: app.iconSize * 4
width: height
name: app.interfaceToIcon(boolView.interfaceName)
color: device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor
name: {
switch (boolView.interfaceName) {
case "closablesensor":
return device.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg")
default:
return app.interfaceToIcon(boolView.interfaceName)
}
}
color: {
switch (boolView.interfaceName) {
case "closablesensor":
return device.states.getState(boolView.stateType.id).value === true ? "green" : "red"
default:
device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor
}
}
}
}
Item {

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="lock.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199992"
inkscape:cx="5.4626288"
inkscape:cy="25.355855"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 393.98438,425.36133 -2,0 -46.01758,0 0,-64 48.01758,0 0,64 z m -4,-4 0,-55.99805 -40.01758,0 0,55.99805 40.01758,0 z"
id="rect4269"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 434.00087,393.36222 c 0,13.81562 -11.1845,25.00195 -25.00013,25.00195 l -16.0161,0 0,-4.00195 16.0161,0 c 11.66838,0 21.0005,-9.33162 21.0005,-21 0,-11.66838 -9.33212,-20.99805 -21.0005,-20.99805 l -16.0161,0 0,-4.00195 16.0161,0 c 13.81563,0 25.00013,11.18438 25.00013,25 z"
id="rect4271"
inkscape:connector-curvature="0" />
<path
transform="matrix(0,-1,-1,0,0,0)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
d="m -397.36221,-377.9787 8,-2.00079 0,20.00794 -8,0 z"
id="rect4280"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
viewBox="0 0 96 96.000001"
sodipodi:docname="lock-open.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.239998"
inkscape:cx="43.592981"
inkscape:cy="47.834493"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="2792"
inkscape:window-height="1698"
inkscape:window-x="88"
inkscape:window-y="44"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080"
inkscape:locked="false" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170"
inkscape:locked="false" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 393.98438,425.36133 -2,0 -46.01758,0 0,-64 48.01758,0 0,64 z m -4,-4 0,-55.99805 -40.01758,0 0,55.99805 40.01758,0 z"
id="rect4269"
inkscape:connector-curvature="0" />
<path
transform="matrix(0,-1,-1,0,0,0)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
d="m -397.36221,-377.9787 8,-2.00079 0,20.00794 -8,0 z"
id="rect4280"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 434.00086,393.36221 c 0,13.81562 -11.18449,25.00196 -25.00012,25.00196 h -16.0161 v -4.00196 h 16.0161 c 11.66838,0 21.00049,-9.33162 21.00049,-21 0,-8.90954 -5.45053,-16.43796 -13.21812,-19.50781 l 1.56898,-3.66211 c 9.17277,3.70153 15.64877,12.66514 15.64877,23.16992 z m -30.01187,-20.99804 h -11.00435 v -4.00196 h 11.00435 z"
id="rect4271-3" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="lock.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6199992"
inkscape:cx="5.4626288"
inkscape:cy="25.355855"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="true"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 393.98438,425.36133 -2,0 -46.01758,0 0,-64 48.01758,0 0,64 z m -4,-4 0,-55.99805 -40.01758,0 0,55.99805 40.01758,0 z"
id="rect4269"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 434.00087,393.36222 c 0,13.81562 -11.1845,25.00195 -25.00013,25.00195 l -16.0161,0 0,-4.00195 16.0161,0 c 11.66838,0 21.0005,-9.33162 21.0005,-21 0,-11.66838 -9.33212,-20.99805 -21.0005,-20.99805 l -16.0161,0 0,-4.00195 16.0161,0 c 13.81563,0 25.00013,11.18438 25.00013,25 z"
id="rect4271"
inkscape:connector-curvature="0" />
<path
transform="matrix(0,-1,-1,0,0,0)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
d="m -397.36221,-377.9787 8,-2.00079 0,20.00794 -8,0 z"
id="rect4280"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg4874" width="96" height="96" version="1.1" viewBox="0 0 96 96" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata id="metadata4879">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1" transform="translate(67.857 -78.505)">
<rect id="rect4782-35" transform="rotate(90)" x="78.505" y="-28.143" width="96" height="96" style="color:#000000;fill:none"/>
<path id="path4116-8" d="m-19.856 84.505c-23.173 0-42 18.827-42 41.999 0 23.172 18.827 41.999 42 41.999 23.173 0 42-18.827 42-41.999 0-23.172-18.827-41.999-42-41.999zm0 3.9984c21.01 0 38 16.99 38 38.001 0 21.011-16.99 38.001-38 38.001-21.01 0-38-16.99-38-38.001 0-21.011 16.99-38.001 38-38.001z" style="color-rendering:auto;color:#000000;fill:#808080;font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:none;font-variant-numeric:normal;font-variant-position:normal;image-rendering:auto;isolation:auto;mix-blend-mode:normal;shape-padding:0;shape-rendering:auto;solid-color:#000000;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
<path id="ellipse4183" d="m-21.663 100.57c2.815-0.19766 5.6957 0.0631 8.5352 0.8239 11.358 3.0433 19.27 13.356 19.27 25.113h-4c0-9.9674-6.6798-18.67-16.307-21.249-9.6269-2.5795-19.761 1.618-24.744 10.25l-3.4648-1.9992c4.4092-7.6369 12.266-12.346 20.711-12.938z" style="color-rendering:auto;color:#000000;fill:#808080;font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:none;font-variant-numeric:normal;font-variant-position:normal;image-rendering:auto;isolation:auto;mix-blend-mode:normal;shape-padding:0;shape-rendering:auto;solid-color:#000000;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
<path id="path4187" d="m-18.052 152.44c-2.815 0.19767-5.6957-0.0612-8.5352-0.82193-11.358-3.0433-19.27-13.356-19.27-25.113h4c0 9.9674 6.6798 18.67 16.307 21.249 9.6269 2.5795 19.761-1.6181 24.744-10.25l3.4668 1.9992c-4.4092 7.6369-12.268 12.344-20.713 12.936z" style="color-rendering:auto;color:#000000;fill:#808080;font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:none;font-variant-numeric:normal;font-variant-position:normal;image-rendering:auto;isolation:auto;mix-blend-mode:normal;shape-padding:0;shape-rendering:auto;solid-color:#000000;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
<path id="path5918" d="m-6.5222 122.42 18.665-7.1843c-0.22375 3.2869-0.5836 6.6835-1.0752 10.19-0.50612 3.481-1.1013 6.8801-1.7886 10.196-2.6998-2.0116-5.4056-4.1394-8.1157-6.3818-2.7182-2.2723-5.2797-4.5458-7.686-6.8213z" style="color:#000000;fill:#808080"/>
<path id="path4471" d="m-33.192 130.59-18.665 7.1843c0.22375-3.2869 0.5836-6.6834 1.0752-10.19 0.50612-3.481 1.1013-6.8801 1.7886-10.196 2.6998 2.0116 5.4056 4.1394 8.1157 6.3818 2.7182 2.2723 5.2797 4.5458 7.686 6.8213z" style="color:#000000;fill:#808080"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -428,6 +428,7 @@ MainPageTile {
ListElement { ifaceName: "pressuresensor"; stateName: "pressure" }
ListElement { ifaceName: "daylightsensor"; stateName: "daylight" }
ListElement { ifaceName: "presencesensor"; stateName: "isPresent" }
ListElement { ifaceName: "closablesensor"; stateName: "closed" }
ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" }
ListElement { ifaceName: "co2sensor"; stateName: "co2" }
ListElement { ifaceName: "conductivity"; stateName: "conductivity" }
@ -511,7 +512,7 @@ MainPageTile {
Led {
Layout.preferredHeight: app.iconSize * .5
Layout.preferredWidth: height
state: sensorsRoot.shownStateType && sensorsRoot.device.stateValue(sensorsRoot.shownStateType.id) === true ? "on" : "off"
state: sensorsRoot.shownStateType && sensorsRoot.device.states.getState(sensorsRoot.shownStateType.id).value === true ? "on" : "off"
visible: sensorsRoot.shownStateType && sensorsRoot.shownStateType.type.toLowerCase() === "bool"
}
}

View File

@ -93,5 +93,22 @@ Page {
}
}
}
Button {
Layout.fillWidth: true
text: qsTr("Reboot %1:core").arg(app.systemName)
visible: engine.systemController.powerManagementAvailable
onClicked: {
engine.systemController.reboot()
}
}
Button {
Layout.fillWidth: true
text: qsTr("Shutdown %1:core").arg(app.systemName)
visible: engine.systemController.powerManagementAvailable
onClicked: {
engine.systemController.shutdown()
}
}
}
}

View File

@ -0,0 +1,266 @@
import QtQuick 2.8
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.3
import "../components"
import Nymea 1.0
Page {
id: root
header: GuhHeader {
text: qsTr("System update")
backButtonVisible: true
onBackPressed: pageStack.pop()
}
PackagesFilterModel {
id: updatesModel
packages: engine.systemController.packages
updatesOnly: true
}
ColumnLayout {
id: contentColumn
anchors.fill: parent
MeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Configure updates")
onClicked: {
pageStack.push(repositoryListComponent)
}
}
MeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Show all packages")
onClicked: {
pageStack.push(packageListComponent, {packages: engine.systemController.packages})
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.margins: app.margins
elide: Text.ElideRight
text: updatesModel.count === 0 ? qsTr("Your system is up to date.") : qsTr("There are %1 updates available.").arg(updatesModel.count)
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Update all")
visible: updatesModel.count > 0
onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1:core might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/system-update.svg",
title: qsTr("System update"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.updatePackages()
})
}
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: updatesModel
clip: true
delegate: MeaListItemDelegate {
width: parent.width
text: model.displayName
subText: model.candidateVersion
prominentSubText: false
iconName: model.updateAvailable
? Qt.resolvedUrl("../images/system-update.svg")
: Qt.resolvedUrl("../images/view-" + (model.installedVersion.length > 0 ? "expand" : "collapse") + ".svg")
iconColor: model.updateAvailable
? "green"
: model.installedVersion.length > 0 ? "blue" : iconKeyColor
onClicked: {
pageStack.push(packageDetailsComponent, {pkg: updatesModel.get(index)})
}
}
}
}
Component {
id: repositoryListComponent
Page {
id: repositoryListPage
header: GuhHeader {
text: qsTr("Configure update sources")
onBackPressed: pageStack.pop()
}
ListView {
anchors.fill: parent
model: engine.systemController.repositories
delegate: CheckDelegate {
width: parent.width
text: model.displayName
checked: model.enabled
onClicked: {
if (checked) {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Enabling additional software sources allows to install unreleased %1:core packages.\nThis can potentially break your system and lead to problems.\nPlease only use this if you are sure you want this and consider reporting the issues you find when testing unreleased channels.").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Enable package source"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.enableRepository(model.id, true)
})
popup.rejected.connect(function() {
checked = false
})
} else {
engine.systemController.enableRepository(model.id, false)
}
}
}
}
UpdateRunningOverlay {
visible: engine.systemController.updateRunning
}
}
}
Component {
id: packageListComponent
Page {
id: packageListPage
property var packages: null
header: GuhHeader {
text: qsTr("All packages")
onBackPressed: pageStack.pop()
}
ListView {
anchors.fill: parent
model: PackagesFilterModel {
id: filterModel
packages: packageListPage.packages
}
delegate: MeaListItemDelegate {
width: parent.width
text: model.displayName
subText: model.candidateVersion
prominentSubText: false
iconName: model.updateAvailable
? Qt.resolvedUrl("../images/system-update.svg")
: Qt.resolvedUrl("../images/view-" + (model.installedVersion.length > 0 ? "expand" : "collapse") + ".svg")
iconColor: model.updateAvailable
? "green"
: model.installedVersion.length > 0 ? "blue" : iconKeyColor
onClicked: {
pageStack.push(packageDetailsComponent, {pkg: filterModel.get(index)})
}
}
}
UpdateRunningOverlay {
visible: engine.systemController.updateRunning
}
}
}
Component {
id: packageDetailsComponent
Page {
id: packageDetailsPage
property Package pkg: null
header: GuhHeader {
text: pkg.displayName
onBackPressed: pageStack.pop()
}
GridLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
columns: app.landscape ? 2 : 1
MeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Installed version:")
subText: packageDetailsPage.pkg.installedVersion
progressive: false
}
MeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Candidate version:")
subText: packageDetailsPage.pkg.candidateVersion
visible: packageDetailsPage.pkg.updateAvailable || packageDetailsPage.pkg.installedVersion.length === 0
progressive: false
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
visible: packageDetailsPage.pkg.updateAvailable || packageDetailsPage.pkg.installedVersion.length === 0
text: packageDetailsPage.pkg.updateAvailable ? qsTr("Update") : qsTr("Install")
onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1:core might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/system-update.svg",
title: qsTr("Start update"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.updatePackages(packageDetailsPage.pkg.id)
})
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Remove")
visible: packageDetailsPage.pkg.canRemove
onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1:core might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/system-update.svg",
title: qsTr("Remove package"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.removePackages(packageDetailsPage.pkg.id)
})
}
}
}
UpdateRunningOverlay {
visible: engine.systemController.updateRunning
}
}
}
UpdateRunningOverlay {
visible: engine.systemController.updateRunning
}
}