Merge PR #316: Add support for changing the password and manage tokens

This commit is contained in:
Jenkins nymea 2020-02-26 19:43:35 +01:00
commit 1b7f8d3bfd
39 changed files with 2472 additions and 1708 deletions

View File

@ -164,7 +164,7 @@ void NymeaDiscovery::cacheHost(NymeaHost *host)
settings.beginGroup(QString::number(i++)); settings.beginGroup(QString::number(i++));
settings.setValue("url", connection->url()); settings.setValue("url", connection->url());
settings.setValue("bearerType", connection->bearerType()); settings.setValue("bearerType", connection->bearerType());
settings.value("secure", connection->secure()); settings.setValue("secure", connection->secure());
settings.setValue("displayName", connection->displayName()); settings.setValue("displayName", connection->displayName());
settings.endGroup(); settings.endGroup();
} }

View File

@ -161,6 +161,7 @@ Connection *NymeaConnection::currentConnection() const
if (!m_currentHost || !m_currentTransport) { if (!m_currentHost || !m_currentTransport) {
return nullptr; return nullptr;
} }
qDebug() << "secure:" << m_transportCandidates.value(m_currentTransport)->secure();
return m_transportCandidates.value(m_currentTransport); return m_transportCandidates.value(m_currentTransport);
} }

View File

@ -189,6 +189,11 @@ bool JsonRpcClient::pushButtonAuthAvailable() const
return m_pushButtonAuthAvailable; return m_pushButtonAuthAvailable;
} }
bool JsonRpcClient::authenticated() const
{
return m_authenticated;
}
JsonRpcClient::CloudConnectionState JsonRpcClient::cloudConnectionState() const JsonRpcClient::CloudConnectionState JsonRpcClient::cloudConnectionState() const
{ {
return m_cloudConnectionState; return m_cloudConnectionState;
@ -297,6 +302,9 @@ void JsonRpcClient::processAuthenticate(const QVariantMap &data)
settings.endGroup(); settings.endGroup();
emit authenticationRequiredChanged(); emit authenticationRequiredChanged();
m_authenticated = true;
emit authenticated();
setNotificationsEnabled(); setNotificationsEnabled();
} else { } else {
qWarning() << "Authentication failed" << data; qWarning() << "Authentication failed" << data;
@ -376,6 +384,7 @@ void JsonRpcClient::onInterfaceConnectedChanged(bool connected)
qDebug() << "JsonRpcClient: Transport disconnected."; qDebug() << "JsonRpcClient: Transport disconnected.";
m_initialSetupRequired = false; m_initialSetupRequired = false;
m_authenticationRequired = false; m_authenticationRequired = false;
m_authenticated = false;
m_serverQtVersion.clear(); m_serverQtVersion.clear();
m_serverQtBuildVersion.clear(); m_serverQtBuildVersion.clear();
if (m_connected) { if (m_connected) {
@ -442,6 +451,8 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
settings.setValue(m_serverUuid, m_token); settings.setValue(m_serverUuid, m_token);
settings.endGroup(); settings.endGroup();
emit authenticationRequiredChanged(); emit authenticationRequiredChanged();
m_authenticated = false;
emit authenticatedChanged();
} }
if (!reply->caller().isNull() && !reply->callback().isEmpty()) { if (!reply->caller().isNull() && !reply->callback().isEmpty()) {
@ -508,11 +519,13 @@ void JsonRpcClient::helloReply(const QVariantMap &params)
if (m_token.isEmpty()) { if (m_token.isEmpty()) {
return; return;
} }
m_authenticated = true;
emit authenticatedChanged();
} }
setNotificationsEnabled(); setNotificationsEnabled();
getCloudConnectionStatus(); getCloudConnectionStatus();
} }
} }

View File

@ -50,6 +50,7 @@ class JsonRpcClient : public JsonHandler
Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged) Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged)
Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged) Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged)
Q_PROPERTY(bool pushButtonAuthAvailable READ pushButtonAuthAvailable NOTIFY pushButtonAuthAvailableChanged) Q_PROPERTY(bool pushButtonAuthAvailable READ pushButtonAuthAvailable NOTIFY pushButtonAuthAvailableChanged)
Q_PROPERTY(bool authenticated READ authenticated NOTIFY authenticatedChanged)
Q_PROPERTY(CloudConnectionState cloudConnectionState READ cloudConnectionState NOTIFY cloudConnectionStateChanged) Q_PROPERTY(CloudConnectionState cloudConnectionState READ cloudConnectionState NOTIFY cloudConnectionStateChanged)
Q_PROPERTY(QString serverVersion READ serverVersion NOTIFY handshakeReceived) Q_PROPERTY(QString serverVersion READ serverVersion NOTIFY handshakeReceived)
Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived) Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived)
@ -81,6 +82,7 @@ public:
bool initialSetupRequired() const; bool initialSetupRequired() const;
bool authenticationRequired() const; bool authenticationRequired() const;
bool pushButtonAuthAvailable() const; bool pushButtonAuthAvailable() const;
bool authenticated() const;
CloudConnectionState cloudConnectionState() const; CloudConnectionState cloudConnectionState() const;
void deployCertificate(const QByteArray &rootCA, const QByteArray &certificate, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint); void deployCertificate(const QByteArray &rootCA, const QByteArray &certificate, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint);
@ -103,6 +105,7 @@ signals:
void initialSetupRequiredChanged(); void initialSetupRequiredChanged();
void authenticationRequiredChanged(); void authenticationRequiredChanged();
void pushButtonAuthAvailableChanged(); void pushButtonAuthAvailableChanged();
void authenticatedChanged();
void connectedChanged(bool connected); void connectedChanged(bool connected);
void tokenChanged(); void tokenChanged();
void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion); void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion);
@ -135,6 +138,7 @@ private:
bool m_initialSetupRequired = false; bool m_initialSetupRequired = false;
bool m_authenticationRequired = false; bool m_authenticationRequired = false;
bool m_pushButtonAuthAvailable = false; bool m_pushButtonAuthAvailable = false;
bool m_authenticated = false;
CloudConnectionState m_cloudConnectionState = CloudConnectionStateDisabled; CloudConnectionState m_cloudConnectionState = CloudConnectionStateDisabled;
int m_pendingPushButtonTransaction = -1; int m_pendingPushButtonTransaction = -1;
QString m_serverUuid; QString m_serverUuid;

View File

@ -105,6 +105,10 @@
#include "types/script.h" #include "types/script.h"
#include "types/scripts.h" #include "types/scripts.h"
#include "types/types.h" #include "types/types.h"
#include "usermanager.h"
#include "types/tokeninfos.h"
#include "types/tokeninfo.h"
#include "types/userinfo.h"
#include <QtQml/qqml.h> #include <QtQml/qqml.h>
@ -281,6 +285,11 @@ void registerQmlTypes() {
qmlRegisterType<ScriptSyntaxHighlighter>(uri, 1, 0, "ScriptSyntaxHighlighter"); qmlRegisterType<ScriptSyntaxHighlighter>(uri, 1, 0, "ScriptSyntaxHighlighter");
qmlRegisterType<CodeCompletion>(uri, 1, 0, "CodeCompletion"); qmlRegisterType<CodeCompletion>(uri, 1, 0, "CodeCompletion");
qmlRegisterUncreatableType<CompletionProxyModel>(uri, 1, 0, "CompletionModel", "Get it from ScriptSyntaxHighlighter"); qmlRegisterUncreatableType<CompletionProxyModel>(uri, 1, 0, "CompletionModel", "Get it from ScriptSyntaxHighlighter");
qmlRegisterType<UserManager>(uri, 1, 0, "UserManager");
qmlRegisterUncreatableType<UserInfo>(uri, 1, 0, "UserInfo", "Get it from UserManager");
qmlRegisterUncreatableType<TokenInfo>(uri, 1, 0, "TokenInfo", "Get it from TokenInfos");
qmlRegisterUncreatableType<TokenInfos>(uri, 1, 0, "TokenInfos", "Get it from UserManager");
} }
#endif // LIBNYMEAAPPCORE_H #endif // LIBNYMEAAPPCORE_H

View File

@ -55,6 +55,7 @@ SOURCES += \
scripting/completionmodel.cpp \ scripting/completionmodel.cpp \
scriptmanager.cpp \ scriptmanager.cpp \
scriptsyntaxhighlighter.cpp \ scriptsyntaxhighlighter.cpp \
usermanager.cpp \
vendorsproxy.cpp \ vendorsproxy.cpp \
pluginsproxy.cpp \ pluginsproxy.cpp \
interfacesmodel.cpp \ interfacesmodel.cpp \
@ -91,6 +92,7 @@ SOURCES += \
models/devicemodel.cpp \ models/devicemodel.cpp \
system/systemcontroller.cpp \ system/systemcontroller.cpp \
HEADERS += \ HEADERS += \
configuration/networkmanager.h \ configuration/networkmanager.h \
engine.h \ engine.h \
@ -122,6 +124,7 @@ HEADERS += \
scripting/completionmodel.h \ scripting/completionmodel.h \
scriptmanager.h \ scriptmanager.h \
scriptsyntaxhighlighter.h \ scriptsyntaxhighlighter.h \
usermanager.h \
vendorsproxy.h \ vendorsproxy.h \
pluginsproxy.h \ pluginsproxy.h \
interfacesmodel.h \ interfacesmodel.h \

View File

@ -0,0 +1,129 @@
#include "usermanager.h"
#include "types/tokeninfo.h"
#include <QDebug>
#include <QMetaEnum>
UserManager::UserManager(QObject *parent):
JsonHandler(parent)
{
m_userInfo = new UserInfo(this);
m_tokenInfos = new TokenInfos(this);
}
Engine *UserManager::engine() const
{
return m_engine;
}
void UserManager::setEngine(Engine *engine)
{
if (m_engine != engine) {
m_engine = engine;
m_engine->jsonRpcClient()->registerNotificationHandler(this, "notificationReceived");
emit engineChanged();
m_loading = true;
emit loadingChanged();
m_engine->jsonRpcClient()->sendCommand("Users.GetUserInfo", QVariantMap(), this, "getUserInfoReply");
m_engine->jsonRpcClient()->sendCommand("Users.GetTokens", QVariantMap(), this, "getTokensReply");
}
}
bool UserManager::loading() const
{
return m_loading;
}
UserInfo *UserManager::userInfo() const
{
return m_userInfo;
}
TokenInfos *UserManager::tokenInfos() const
{
return m_tokenInfos;
}
QString UserManager::nameSpace() const
{
return "Users";
}
int UserManager::changePassword(const QString &newPassword)
{
QVariantMap params;
params.insert("newPassword", newPassword);
int callId = m_engine->jsonRpcClient()->sendCommand("Users.ChangePassword", params, this, "changePasswordReply");
return callId;
}
int UserManager::removeToken(const QUuid &id)
{
QVariantMap params;
params.insert("tokenId", id);
int callId = m_engine->jsonRpcClient()->sendCommand("Users.RemoveToken", params, this, "deleteTokenReply");
m_tokensToBeRemoved.insert(callId, id);
return callId;
}
void UserManager::notificationReceived(const QVariantMap &data)
{
qDebug() << "Users notification" << data;
}
void UserManager::getUserInfoReply(const QVariantMap &data)
{
qDebug() << "User info reply" << data;
m_userInfo->setUsername(data.value("params").toMap().value("userInfo").toMap().value("username").toString());
}
void UserManager::getTokensReply(const QVariantMap &data)
{
foreach (const QVariant &tokenVariant, data.value("params").toMap().value("tokenInfoList").toList()) {
// qDebug() << "Token received" << tokenVariant.toMap();
QVariantMap token = tokenVariant.toMap();
QUuid id = token.value("id").toString();
QString username = token.value("username").toString();
QString deviceName = token.value("deviceName").toString();
QDateTime creationTime = QDateTime::fromSecsSinceEpoch(token.value("creationTime").toInt());
TokenInfo *tokenInfo = new TokenInfo(id, username, deviceName, creationTime);
m_tokenInfos->addToken(tokenInfo);
}
}
void UserManager::deleteTokenReply(const QVariantMap &payload)
{
qDebug() << "Delete token reply" << payload;
int callId = payload.value("id").toInt();
QUuid tokenId = m_tokensToBeRemoved.take(callId);
QVariantMap params = payload.value("params").toMap();
QString errorString = params.value("error").toString();
QMetaEnum metaEnum = QMetaEnum::fromType<UserManager::UserError>();
UserError error = static_cast<UserError>(metaEnum.keyToValue(errorString.toUtf8()));
emit deleteTokenResponse(callId, error);
if (error == UserErrorNoError) {
m_tokenInfos->removeToken(tokenId);
}
}
void UserManager::changePasswordReply(const QVariantMap &data)
{
qDebug() << "Change password reply" << data;
int callId = data.value("id").toInt();
QVariantMap params = data.value("params").toMap();
QString errorString = params.value("error").toString();
QMetaEnum metaEnum = QMetaEnum::fromType<UserManager::UserError>();
UserError error = static_cast<UserError>(metaEnum.keyToValue(errorString.toUtf8()));
emit changePasswordResponse(callId, error);
}

View File

@ -0,0 +1,73 @@
#ifndef USERMANAGER_H
#define USERMANAGER_H
#include <QObject>
#include "jsonrpc/jsonrpcclient.h"
#include "engine.h"
#include "types/tokeninfos.h"
#include "types/userinfo.h"
class UserManager: public JsonHandler
{
Q_OBJECT
Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged)
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
Q_PROPERTY(UserInfo* userInfo READ userInfo CONSTANT)
Q_PROPERTY(TokenInfos* tokenInfos READ tokenInfos CONSTANT)
public:
enum UserError {
UserErrorNoError,
UserErrorBackendError,
UserErrorInvalidUserId,
UserErrorDuplicateUserId,
UserErrorBadPassword,
UserErrorTokenNotFound,
UserErrorPermissionDenied
};
Q_ENUM(UserError)
explicit UserManager(QObject *parent = nullptr);
Engine* engine() const;
void setEngine(Engine* engine);
bool loading() const;
UserInfo* userInfo() const;
TokenInfos* tokenInfos() const;
QString nameSpace() const override;
Q_INVOKABLE int changePassword(const QString &newPassword);
Q_INVOKABLE int removeToken(const QUuid &id);
signals:
void engineChanged();
void loadingChanged();
void deleteTokenResponse(int id, UserError error);
void changePasswordResponse(int id, UserError error);
private slots:
void notificationReceived(const QVariantMap &data);
void getUserInfoReply(const QVariantMap &data);
void getTokensReply(const QVariantMap &data);
void deleteTokenReply(const QVariantMap &data);
void changePasswordReply(const QVariantMap &data);
private:
Engine *m_engine = nullptr;
bool m_loading = false;
UserInfo *m_userInfo = nullptr;
TokenInfos *m_tokenInfos = nullptr;
QHash<int, QUuid> m_tokensToBeRemoved;
};
#endif // USERMANAGER_H

View File

@ -64,6 +64,10 @@ HEADERS += \
types/tags.h \ types/tags.h \
types/wirelessaccesspoint.h \ types/wirelessaccesspoint.h \
types/wirelessaccesspoints.h \ types/wirelessaccesspoints.h \
types/tokeninfo.h \
types/tokeninfos.h \
types/userinfo.h \
SOURCES += \ SOURCES += \
types/browseritem.cpp \ types/browseritem.cpp \
@ -122,3 +126,8 @@ SOURCES += \
types/tags.cpp \ types/tags.cpp \
types/wirelessaccesspoint.cpp \ types/wirelessaccesspoint.cpp \
types/wirelessaccesspoints.cpp \ types/wirelessaccesspoints.cpp \
types/tokeninfo.cpp \
types/tokeninfos.cpp \
types/userinfo.cpp \

View File

@ -0,0 +1,31 @@
#include "tokeninfo.h"
TokenInfo::TokenInfo(const QUuid &id, const QString &username, const QString &deviceName, const QDateTime &creationTime, QObject *parent):
QObject(parent),
m_id(id),
m_username(username),
m_deviceName(deviceName),
m_creationTime(creationTime)
{
}
QUuid TokenInfo::id() const
{
return m_id;
}
QString TokenInfo::username() const
{
return m_username;
}
QString TokenInfo::deviceName() const
{
return m_deviceName;
}
QDateTime TokenInfo::creationTime() const
{
return m_creationTime;
}

View File

@ -0,0 +1,26 @@
#ifndef TOKENINFO_H
#define TOKENINFO_H
#include <QObject>
#include <QUuid>
#include <QDateTime>
class TokenInfo : public QObject
{
Q_OBJECT
public:
explicit TokenInfo(const QUuid &id, const QString &username, const QString &deviceName, const QDateTime &creationTime, QObject *parent = nullptr);
QUuid id() const;
QString username() const;
QString deviceName() const;
QDateTime creationTime() const;
private:
QUuid m_id;
QString m_username;
QString m_deviceName;
QDateTime m_creationTime;
};
#endif // TOKENINFO_H

View File

@ -0,0 +1,60 @@
#include "tokeninfos.h"
#include "tokeninfo.h"
TokenInfos::TokenInfos(QObject *parent) : QAbstractListModel(parent)
{
}
int TokenInfos::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant TokenInfos::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleId:
return m_list.at(index.row())->id();
case RoleUsername:
return m_list.at(index.row())->username();
case RoleDeviceName:
return m_list.at(index.row())->deviceName();
case RoleCreationTime:
return m_list.at(index.row())->creationTime();
}
return QVariant();
}
QHash<int, QByteArray> TokenInfos::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleId, "id");
roles.insert(RoleUsername, "username");
roles.insert(RoleDeviceName, "deviceName");
roles.insert(RoleCreationTime, "creationTime");
return roles;
}
void TokenInfos::addToken(TokenInfo *tokenInfo)
{
tokenInfo->setParent(this);
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
m_list.append(tokenInfo);
endInsertRows();
emit countChanged();
}
void TokenInfos::removeToken(const QUuid &tokenId)
{
for (int i = 0; i < m_list.count(); i++) {
if (m_list.at(i)->id() == tokenId) {
beginRemoveRows(QModelIndex(), i, i);
m_list.takeAt(i)->deleteLater();
endRemoveRows();
emit countChanged();
return;
}
}
}

View File

@ -0,0 +1,36 @@
#ifndef TOKENINFOS_H
#define TOKENINFOS_H
#include <QAbstractListModel>
class TokenInfo;
class TokenInfos : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RoleId,
RoleUsername,
RoleDeviceName,
RoleCreationTime
};
explicit TokenInfos(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 addToken(TokenInfo *tokenInfo);
void removeToken(const QUuid &tokenId);
signals:
void countChanged();
private:
QList<TokenInfo*> m_list;
};
#endif // TOKENINFOS_H

View File

@ -0,0 +1,27 @@
#include "userinfo.h"
UserInfo::UserInfo(QObject *parent):
QObject(parent)
{
}
UserInfo::UserInfo(const QString &username, QObject *parent):
QObject(parent),
m_username(username)
{
}
QString UserInfo::username() const
{
return m_username;
}
void UserInfo::setUsername(const QString &username)
{
if (m_username != username) {
m_username = username;
emit usernameChanged();
}
}

View File

@ -0,0 +1,25 @@
#ifndef USERINFO_H
#define USERINFO_H
#include <QObject>
class UserInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QString username READ username NOTIFY usernameChanged)
public:
explicit UserInfo(QObject *parent = nullptr);
explicit UserInfo(const QString &username, QObject *parent = nullptr);
QString username() const;
void setUsername(const QString &username);
signals:
void usernameChanged();
private:
QString m_username;
};
#endif // USERINFO_H

View File

@ -216,5 +216,6 @@
<file>ui/images/save.svg</file> <file>ui/images/save.svg</file>
<file>ui/images/edit-clear.svg</file> <file>ui/images/edit-clear.svg</file>
<file>ui/images/smartlock.svg</file> <file>ui/images/smartlock.svg</file>
<file>ui/images/key.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -208,5 +208,8 @@
<file>ui/magic/scripting/LineNumbers.qml</file> <file>ui/magic/scripting/LineNumbers.qml</file>
<file>ui/magic/scripting/CompletionBox.qml</file> <file>ui/magic/scripting/CompletionBox.qml</file>
<file>ui/magic/scripting/EditorPane.qml</file> <file>ui/magic/scripting/EditorPane.qml</file>
<file>ui/system/UsersSettingsPage.qml</file>
<file>ui/components/SettingsPageBase.qml</file>
<file>ui/components/SettingsPageSectionHeader.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -70,6 +70,23 @@ Page {
} }
} }
Pane {
Layout.fillWidth: true
Material.elevation: layout.isGrid ? 1 : 0
padding: 0
visible: engine.jsonRpcClient.ensureServerVersion("4.2")
&& engine.jsonRpcClient.authenticated
NymeaListItemDelegate {
width: parent.width
iconName: "../images/account.svg"
text: qsTr("User settings")
subText: qsTr("Configure who can log in")
prominentSubText: false
wrapTexts: false
onClicked: pageStack.push(Qt.resolvedUrl("system/UsersSettingsPage.qml"))
}
}
Pane { Pane {
Layout.fillWidth: true Layout.fillWidth: true
Material.elevation: layout.isGrid ? 1 : 0 Material.elevation: layout.isGrid ? 1 : 0

View File

@ -35,46 +35,37 @@ import QtQuick.Layouts 1.1
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("About %1").arg(app.appName)
text: qsTr("About %1").arg(app.appName)
backButtonVisible: true
onBackPressed: pageStack.pop()
}
Flickable { Imprint {
anchors.fill: parent id: imprint
contentHeight: imprint.implicitHeight Layout.fillWidth: true
title: app.appName
additionalLicenses: ListModel {
ListElement { license: "CC-BY-SA-3.0"; component: "Suru icons"; infoText: qsTr("Suru icons by Ubuntu"); platforms: "*" }
ListElement { license: "CC-BY-SA-3.0"; component: "Ubuntu font"; infoText: qsTr("Ubuntu font by Ubuntu"); platforms: "*" }
ListElement { license: "LGPL3"; component: "QtZeroConf"; infoText: qsTr("QtZeroConf library by Jonathan Bagg"); platforms: "android,ios,linux,osx" }
ListElement { license: "OpenSSL"; component: "OpenSSL"; infoText: qsTr("OpenSSL libraries by Eric Young"); platforms: "android,windows" }
ListElement { license: "OFL"; component: "Oswald font"; infoText: qsTr("Oswald font by The Oswald Project"); platforms: "*" }
}
Imprint { githubLink: "https://github.com/nymea/nymea-app"
id: imprint
width: parent.width
title: app.appName
additionalLicenses: ListModel {
ListElement { license: "CC-BY-SA-3.0"; component: "Suru icons"; infoText: qsTr("Suru icons by Ubuntu"); platforms: "*" }
ListElement { license: "CC-BY-SA-3.0"; component: "Ubuntu font"; infoText: qsTr("Ubuntu font by Ubuntu"); platforms: "*" }
ListElement { license: "LGPL3"; component: "QtZeroConf"; infoText: qsTr("QtZeroConf library by Jonathan Bagg"); platforms: "android,ios,linux,osx" }
ListElement { license: "OpenSSL"; component: "OpenSSL"; infoText: qsTr("OpenSSL libraries by Eric Young"); platforms: "android,windows" }
ListElement { license: "OFL"; component: "Oswald font"; infoText: qsTr("Oswald font by The Oswald Project"); platforms: "*" }
}
githubLink: "https://github.com/nymea/nymea-app" NymeaListItemDelegate {
Layout.fillWidth: true
NymeaListItemDelegate { text: qsTr("App version:")
Layout.fillWidth: true subText: appVersion
text: qsTr("App version:") progressive: false
subText: appVersion prominentSubText: false
progressive: false }
prominentSubText: false NymeaListItemDelegate {
} Layout.fillWidth: true
NymeaListItemDelegate { text: qsTr("Qt version:")
Layout.fillWidth: true subText: qtVersion + (qtBuildVersion !== qtVersion ? " (" + qsTr("Built with %1").arg(qtBuildVersion) + ")" : "")
text: qsTr("Qt version:") progressive: false
subText: qtVersion + (qtBuildVersion !== qtVersion ? " (" + qsTr("Built with %1").arg(qtBuildVersion) + ")" : "") prominentSubText: false
progressive: false
prominentSubText: false
}
} }
} }
} }

View File

@ -87,12 +87,11 @@ Page {
Pane { Pane {
Layout.fillWidth: true Layout.fillWidth: true
Material.elevation: layout.isGrid ? 1 : 0 Material.elevation: layout.isGrid ? 1 : 0
visible: settings.showHiddenOptions
padding: 0 padding: 0
NymeaListItemDelegate { NymeaListItemDelegate {
width: parent.width width: parent.width
text: qsTr("Developer options") text: qsTr("Developer options")
subText: qsTr("Yeehaaa!") subText: qsTr("Access tools for debugging and error reporting")
iconName: "../images/sdk.svg" iconName: "../images/sdk.svg"
prominentSubText: false prominentSubText: false
wrapTexts: false wrapTexts: false

View File

@ -34,12 +34,9 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("%1 cloud login").arg(app.appName)
text: qsTr("Cloud login")
onBackPressed: pageStack.pop()
}
Component.onCompleted: { Component.onCompleted: {
if (AWSClient.isLoggedIn) { if (AWSClient.isLoggedIn) {
@ -50,13 +47,13 @@ Page {
Connections { Connections {
target: AWSClient target: AWSClient
onLoginResult: { onLoginResult: {
busyOverlay.shown = false; root.busy = false;
if (error === AWSClient.LoginErrorNoError) { if (error === AWSClient.LoginErrorNoError) {
AWSClient.fetchDevices(); AWSClient.fetchDevices();
} }
} }
onDeleteAccountResult: { onDeleteAccountResult: {
busyOverlay.shown = false; root.busy = false;
if (error !== AWSClient.LoginErrorNoError) { if (error !== AWSClient.LoginErrorNoError) {
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml")); var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var text = qsTr("Sorry, an error happened removing the account. Please try again later."); var text = qsTr("Sorry, an error happened removing the account. Please try again later.");
@ -68,12 +65,15 @@ Page {
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent Layout.fillWidth: true
visible: AWSClient.isLoggedIn visible: AWSClient.isLoggedIn
SettingsPageSectionHeader {
text: qsTr("Login")
}
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: app.margins
Layout.leftMargin: app.margins Layout.leftMargin: app.margins
Layout.rightMargin: app.margins Layout.rightMargin: app.margins
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
@ -89,11 +89,20 @@ Page {
} }
} }
ThinDivider {} RowLayout {
SettingsPageSectionHeader {
text: qsTr("Connected %1:core systems").arg(app.systemName)
}
BusyIndicator {
running: AWSClient.awsDevices.busy
height: app.iconSize
width: height
}
}
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: app.margins
Layout.leftMargin: app.margins Layout.leftMargin: app.margins
Layout.rightMargin: app.margins Layout.rightMargin: app.margins
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
@ -101,13 +110,11 @@ Page {
qsTr("There are no %1:core systems connected to your cloud yet.").arg(app.systemName) : qsTr("There are no %1:core systems connected to your cloud yet.").arg(app.systemName) :
qsTr("There are %n %1:core systems connected to your cloud.", "", AWSClient.awsDevices.count).arg(app.systemName) qsTr("There are %n %1:core systems connected to your cloud.", "", AWSClient.awsDevices.count).arg(app.systemName)
} }
ListView {
Layout.fillWidth: true Repeater {
Layout.fillHeight: true
clip: true
model: AWSClient.awsDevices model: AWSClient.awsDevices
delegate: NymeaListItemDelegate { delegate: NymeaListItemDelegate {
width: parent.width Layout.fillWidth: true
text: model.name text: model.name
subText: model.id subText: model.id
progressive: false progressive: false
@ -129,66 +136,173 @@ Page {
} }
} }
BusyIndicator { }
anchors.centerIn: parent }
visible: AWSClient.awsDevices.busy
ColumnLayout {
id: loginColumn
visible: !AWSClient.isLoggedIn
Layout.fillWidth: true
SettingsPageSectionHeader {
text: qsTr("Login")
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Log %1 in to %2:cloud in order to connect to %2:core systems from anywhere and receive push notifications from %2:core systems.").arg(app.appName).arg(app.systemName)
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
text: qsTr("See our <a href=\"%1\">privacy policy</a> to find out what information is processed.").arg(app.privacyPolicyUrl)
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: "Username (e-mail)"
}
TextField {
id: usernameTextField
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
placeholderText: "john.smith@cooldomain.com"
inputMethodHints: Qt.ImhEmailCharactersOnly
validator: RegExpValidator { regExp:/\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/ }
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Password")
}
RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
signup: false
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("OK")
enabled: usernameTextField.acceptableInput
onClicked: {
root.busy = true
AWSClient.login(usernameTextField.text, passwordTextField.password);
}
}
Connections {
target: AWSClient
onLoginResult: {
switch (error) {
case AWSClient.LoginErrorInvalidUserOrPass:
errorLabel.text = qsTr("Failed to log in. Please try again. Do you perhaps have <a href=\"#\">forgotten your password?</a>")
break;
case AWSClient.LoginErrorNetworkError:
errorLabel.text = qsTr("Failed to connect to the login server. Please mase sure your network connection is working.")
break;
default:
errorLabel.text = qsTr("An unexpected error happened. Please report this isse. Error code: %1").arg(error)
break;
}
errorLabel.visible = (error !== AWSClient.LoginErrorNoError)
}
}
Label {
id: errorLabel
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
color: "red"
visible: false
onLinkActivated: {
pageStack.push(resetPasswordComponent, {email: usernameTextField.text})
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Don't have a user yet?")
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Sign Up")
onClicked: {
pageStack.push(signupPageComponent)
} }
} }
} }
Component { Component {
id : logoutDialogComponent id: signupPageComponent
MeaDialog { SettingsPageBase {
id: logoutDialog id: signupPage
title: qsTr("Goodbye") title: qsTr("Sign up")
text: qsTr("Sorry to see you go. If you log out you won't be able to connect to %1:core systems remotely any more. However, you can come back any time, we'll keep your user account. If you whish to completely delete your account and all the data associated with it, check the box below before hitting ok. If you decide to delete your account, all your personal information will be removed from %1:cloud and cannot be restored.").arg(app.systemName)
headerIcon: "../images/dialog-warning-symbolic.svg"
standardButtons: Dialog.Cancel | Dialog.Ok
RowLayout { Connections {
CheckBox { target: AWSClient
id: deleteCheckbox onSignupResult: {
} signupPage.busy = false;
Label { var text;
Layout.fillWidth: true switch (error) {
wrapMode: Text.WordWrap case AWSClient.LoginErrorNoError:
text: qsTr("Delete my account") pageStack.push(enterCodeComponent)
return;
case AWSClient.LoginErrorInvalidUserOrPass:
text = qsTr("The given username or password are not valid.")
break;
default:
text = qsTr("Uh oh, something went wrong. Please try again.")
}
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var popup = errorDialog.createObject(app, {text: text})
popup.open()
} }
} }
onAccepted: {
if (deleteCheckbox.checked) { SettingsPageSectionHeader {
busyOverlay.shown = true; text: qsTr("Welcome to %1:cloud.").arg(app.systemName)
AWSClient.deleteAccount()
} else {
AWSClient.logout()
}
} }
}
}
Flickable {
anchors.fill: parent
interactive: contentHeight > height
contentHeight: loginColumn.height
visible: !AWSClient.isLoggedIn
ColumnLayout {
id: loginColumn
anchors { left: parent.left; right: parent.right; top: parent.top }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("Log in to %1:cloud in order to connect to %1:core systems from anywhere.").arg(app.systemName) text: qsTr("Please enter your email address and pick a password in order to create a new account.");
onLinkActivated: {
print("clicked", link)
Qt.openUrlExternally(link)
}
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: app.smallFont font.pixelSize: app.smallFont
text: qsTr("See our <a href=\"%1\">privacy policy</a> to find out what information is processed.").arg(app.privacyPolicyUrl) text: qsTr("See our <a href=\"%1\">privacy policy</a> to find out what information is processed. By signing up to %2:cloud you accept those terms and conditions.").arg(app.privacyPolicyUrl).arg(app.systemName)
onLinkActivated: { onLinkActivated: {
Qt.openUrlExternally(link) Qt.openUrlExternally(link)
} }
@ -212,198 +326,32 @@ Page {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Password") text: qsTr("Password")
} }
RowLayout { PasswordTextField {
id: passwordTextField
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
signup: false
}
}
Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins minPasswordLength: 8
text: qsTr("OK") requireLowerCaseLetter: true
enabled: usernameTextField.acceptableInput requireUpperCaseLetter: true
onClicked: { requireNumber: true
busyOverlay.shown = true requireSpecialChar: false
AWSClient.login(usernameTextField.text, passwordTextField.password);
}
}
Connections {
target: AWSClient
onLoginResult: {
switch (error) {
case AWSClient.LoginErrorInvalidUserOrPass:
errorLabel.text = qsTr("Failed to log in. Please try again. Do you perhaps have <a href=\"#\">forgotten your password?</a>")
break;
case AWSClient.LoginErrorNetworkError:
errorLabel.text = qsTr("Failed to connect to the login server. Please mase sure your network connection is working.")
break;
default:
errorLabel.text = qsTr("An unexpected error happened. Please report this isse. Error code: %1").arg(error)
break;
}
errorLabel.visible = (error !== AWSClient.LoginErrorNoError)
}
}
Label {
id: errorLabel
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
color: "red"
visible: false
onLinkActivated: {
pageStack.push(resetPasswordComponent, {email: usernameTextField.text})
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Don't have a user yet?")
} }
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.margins: app.margins
text: qsTr("Sign Up")
onClicked: {
pageStack.push(signupPageComponent)
}
}
}
}
BusyOverlay {
id: busyOverlay
}
Component {
id: signupPageComponent
Page {
id: signupPage
header: NymeaHeader {
text: qsTr("Sign up") text: qsTr("Sign up")
onBackPressed: pageStack.pop() enabled: usernameTextField.acceptableInput && passwordTextField.isValid
} onClicked: {
signupPage.busy = true;
Flickable { AWSClient.signup(usernameTextField.text, passwordTextField.password)
anchors.fill: parent
contentHeight: signupColumn.height
interactive: contentHeight > height
ColumnLayout {
id: signupColumn
anchors { left: parent.left; top: parent.top; right: parent.right }
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Welcome to %1:cloud.").arg(app.systemName)
color: app.accentColor
font.pixelSize: app.largeFont
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Please enter your email address and pick a password in order to create a new account.");
onLinkActivated: {
print("clicked", link)
Qt.openUrlExternally(link)
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
text: qsTr("See our <a href=\"%1\">privacy policy</a> to find out what information is processed. By signing up to %2:cloud you accept those terms and conditions.").arg(app.privacyPolicyUrl).arg(app.systemName)
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: "Username (e-mail)"
}
TextField {
id: usernameTextField
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
placeholderText: "john.smith@cooldomain.com"
inputMethodHints: Qt.ImhEmailCharactersOnly
validator: RegExpValidator { regExp:/\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/ }
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Password")
}
PasswordTextField {
id: passwordTextField
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
Layout.fillWidth: true
minPasswordLength: 8
requireLowerCaseLetter: true
requireUpperCaseLetter: true
requireNumber: true
requireSpecialChar: false
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Sign up")
enabled: usernameTextField.acceptableInput && passwordTextField.isValid
onClicked: {
busyOverlay.shown = true;
AWSClient.signup(usernameTextField.text, passwordTextField.password)
}
}
} }
Connections {
target: AWSClient
onSignupResult: {
busyOverlay.shown = false;
var text;
switch (error) {
case AWSClient.LoginErrorNoError:
pageStack.push(enterCodeComponent)
return;
case AWSClient.LoginErrorInvalidUserOrPass:
text = qsTr("The given username or password are not valid.")
break;
default:
text = qsTr("Uh oh, something went wrong. Please try again.")
}
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var popup = errorDialog.createObject(app, {text: text})
popup.open()
}
}
}
BusyOverlay {
id: busyOverlay
} }
} }
} }
Component { Component {
@ -436,7 +384,7 @@ Page {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("OK") text: qsTr("OK")
onClicked: { onClicked: {
busyOverlay.shown = true; root.busy = true;
AWSClient.confirmRegistration(confirmationCodeTextField.text) AWSClient.confirmRegistration(confirmationCodeTextField.text)
} }
} }
@ -444,7 +392,7 @@ Page {
Connections { Connections {
target: AWSClient target: AWSClient
onConfirmationResult: { onConfirmationResult: {
busyOverlay.shown = false; root.busy = false;
var text var text
switch (error) { switch (error) {
case AWSClient.LoginErrorNoError: case AWSClient.LoginErrorNoError:
@ -473,20 +421,16 @@ Page {
Component { Component {
id: resetPasswordComponent id: resetPasswordComponent
Page { SettingsPageBase {
id: resetPasswordPage id: resetPasswordPage
title: qsTr("Reset password")
property alias email: emailTextField.text property alias email: emailTextField.text
header: NymeaHeader {
text: qsTr("Reset password")
onBackPressed: pageStack.pop()
}
Connections { Connections {
target: AWSClient target: AWSClient
onForgotPasswordResult: { onForgotPasswordResult: {
busyOverlay.shown = false resetPasswordPage.busy = false
if (error !== AWSClient.LoginErrorNoError) { if (error !== AWSClient.LoginErrorNoError) {
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml")); var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var text = qsTr("Sorry, this wasn't right. Did you misspell the email address?"); var text = qsTr("Sorry, this wasn't right. Did you misspell the email address?");
@ -501,38 +445,31 @@ Page {
} }
} }
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
spacing: app.margins
Label { Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.fillWidth: true
wrapMode: Text.WordWrap Layout.margins: app.margins
text: qsTr("Password forgotten?") wrapMode: Text.WordWrap
font.pixelSize: app.largeFont text: qsTr("Password forgotten?")
color: app.accentColor font.pixelSize: app.largeFont
} color: app.accentColor
Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("No problem. Enter your email address here and we'll send you a confirmation code to change your password.")
}
TextField {
id: emailTextField
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
}
Button {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Reset password")
onClicked: {
AWSClient.forgotPassword(emailTextField.text)
busyOverlay.shown = true
}
}
} }
Label {
BusyOverlay { Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
id: busyOverlay wrapMode: Text.WordWrap
text: qsTr("No problem. Enter your email address here and we'll send you a confirmation code to change your password.")
}
TextField {
id: emailTextField
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
}
Button {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Reset password")
onClicked: {
AWSClient.forgotPassword(emailTextField.text)
resetPasswordPage.busy = true
}
} }
} }
} }
@ -540,13 +477,13 @@ Page {
Component { Component {
id: confirmResetPasswordComponent id: confirmResetPasswordComponent
Page { SettingsPageBase {
id: confirmResetPasswordPage id: confirmResetPasswordPage
Connections { Connections {
target: AWSClient target: AWSClient
onConfirmForgotPasswordResult: { onConfirmForgotPasswordResult: {
busyOverlay.shown = false confirmResetPasswordPage.busy = false
if (error !== AWSClient.LoginErrorNoError) { if (error !== AWSClient.LoginErrorNoError) {
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml")); var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var popup = errorDialog.createObject(app, {text: qsTr("Sorry, couldn't reset your password. Did you enter the wrong confirmation code?")}) var popup = errorDialog.createObject(app, {text: qsTr("Sorry, couldn't reset your password. Did you enter the wrong confirmation code?")})
@ -564,63 +501,85 @@ Page {
} }
property string email property string email
header: NymeaHeader { title: qsTr("Reset password")
text: qsTr("Reset password")
onBackPressed: pageStack.pop() Label {
Layout.fillWidth: true
Layout.margins: app.margins
wrapMode: Text.WordWrap
text: qsTr("Check your email!")
color: app.accentColor
font.pixelSize: app.largeFont
} }
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
spacing: app.margins
Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins;
wrapMode: Text.WordWrap
text: qsTr("Enter the confirmation code you've received and a new password for your user %1.").arg(confirmResetPasswordPage.email)
}
Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins;
text: qsTr("Confirmation code:")
}
TextField {
id: codeTextField
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
}
Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins;
text: qsTr("Pick a new password:")
}
PasswordTextField {
id: passwordTextField
minPasswordLength: 8
requireLowerCaseLetter: true
requireUpperCaseLetter: true
requireNumber: true
requireSpecialChar: false
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
}
Button {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Reset password")
enabled: passwordTextField.isValid && codeTextField.text.length > 0
onClicked: {
confirmResetPasswordPage.busy = true
AWSClient.confirmForgotPassword(confirmResetPasswordPage.email, codeTextField.text, passwordTextField.password)
}
}
}
}
Component {
id : logoutDialogComponent
MeaDialog {
id: logoutDialog
title: qsTr("Goodbye")
text: qsTr("Sorry to see you go. If you log out you won't be able to connect to %1:core systems remotely any more. However, you can come back any time, we'll keep your user account. If you whish to completely delete your account and all the data associated with it, check the box below before hitting ok. If you decide to delete your account, all your personal information will be removed from %1:cloud and cannot be restored.").arg(app.systemName)
headerIcon: "../images/dialog-warning-symbolic.svg"
standardButtons: Dialog.Cancel | Dialog.Ok
RowLayout {
CheckBox {
id: deleteCheckbox
}
Label { Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: qsTr("Check your email!") text: qsTr("Delete my account")
color: app.accentColor
font.pixelSize: app.largeFont
} }
}
Label { onAccepted: {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; if (deleteCheckbox.checked) {
wrapMode: Text.WordWrap root.busy = true;
text: qsTr("Enter the confirmation code you've received and a new password for your user %1.").arg(confirmResetPasswordPage.email) AWSClient.deleteAccount()
} } else {
AWSClient.logout()
Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins;
text: qsTr("Confirmation code:")
}
TextField {
id: codeTextField
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
}
Label {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins;
text: qsTr("Pick a new password:")
}
PasswordTextField {
id: passwordTextField
minPasswordLength: 8
requireLowerCaseLetter: true
requireUpperCaseLetter: true
requireNumber: true
requireSpecialChar: false
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
}
Button {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Reset password")
enabled: passwordTextField.isValid && codeTextField.text.length > 0
onClicked: {
busyOverlay.shown = true
AWSClient.confirmForgotPassword(confirmResetPasswordPage.email, codeTextField.text, passwordTextField.password)
}
}
BusyOverlay {
id: busyOverlay
} }
} }
} }

View File

@ -34,63 +34,49 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("Developer options")
text: qsTr("Developer options")
backButtonVisible: true SettingsPageSectionHeader {
onBackPressed: pageStack.pop() text: qsTr("Logging")
} }
ColumnLayout { CheckDelegate {
anchors { left: parent.left; top: parent.top; right: parent.right } text: qsTr("Enable app logging")
enabled: AppLogController.canWriteLogs
checked: AppLogController.enabled
onCheckedChanged: AppLogController.enabled = checked;
Layout.fillWidth: true
}
RowLayout { NymeaListItemDelegate {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins Layout.fillWidth: true
text: qsTr("View log")
onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/AppLogPage.qml"))
enabled: AppLogController.enabled
}
Label {
Layout.fillWidth: true
text: qsTr("Cloud environment")
}
ComboBox { SettingsPageSectionHeader {
currentIndex: model.indexOf(app.settings.cloudEnvironment) text: qsTr("Advanced options")
model: AWSClient.availableConfigs visible: settings.showHiddenOptions
onActivated: { }
app.settings.cloudEnvironment = model[index];
}
}
}
CheckDelegate { RowLayout {
text: qsTr("Enable app logging") Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
enabled: AppLogController.canWriteLogs visible: settings.showHiddenOptions
checked: AppLogController.enabled
onCheckedChanged: AppLogController.enabled = checked; Label {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Cloud environment")
} }
NymeaListItemDelegate { ComboBox {
Layout.fillWidth: true currentIndex: model.indexOf(app.settings.cloudEnvironment)
text: qsTr("View log") model: AWSClient.availableConfigs
onClicked: pageStack.push(Qt.resolvedUrl("../appsettings/AppLogPage.qml")) onActivated: {
enabled: AppLogController.enabled app.settings.cloudEnvironment = model[index];
}
RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
Label {
Layout.fillWidth: true
text: qsTr("Experience mode")
}
ComboBox {
currentIndex: model.indexOf(styleController.currentExperience)
model: styleController.allExperiences
onActivated: {
styleController.currentExperience = model[index]
}
} }
} }
} }

View File

@ -35,167 +35,195 @@ import QtQuick.Layouts 1.1
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("Look and feel")
text: qsTr("Look and feel")
backButtonVisible: true SettingsPageSectionHeader {
onBackPressed: pageStack.pop() text: qsTr("Appearance")
} }
ColumnLayout { RowLayout {
id: contentColumn Layout.fillWidth: true
width: parent.width Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
RowLayout { visible: appBranding.length === 0
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins Label {
visible: !kioskMode && Qt.platform.os !== "ios"
Label {
Layout.fillWidth: true
text: qsTr("View mode")
}
ComboBox {
model: [qsTr("Windowed"), qsTr("Maximized"), qsTr("Fullscreen"), qsTr("Automatic")]
currentIndex: {
switch (settings.viewMode) {
case ApplicationWindow.Windowed:
return 0;
case ApplicationWindow.Maximized:
return 1;
case ApplicationWindow.FullScreen:
return 2;
case ApplicationWindow.AutomaticVisibility:
return 3;
}
}
onActivated: {
switch (currentIndex) {
case 0:
settings.viewMode = ApplicationWindow.Windowed;
break;
case 1:
settings.viewMode = ApplicationWindow.Maximized;
break;
case 2:
settings.viewMode = ApplicationWindow.FullScreen;
break;
case 3:
settings.viewMode = ApplicationWindow.AutomaticVisibility;
break;
}
}
}
}
RowLayout {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
visible: appBranding.length === 0
Label {
Layout.fillWidth: true
text: "Style"
}
ComboBox {
model: styleController.allStyles
currentIndex: styleController.allStyles.indexOf(styleController.currentStyle)
onActivated: {
styleController.currentStyle = model[index]
}
}
Connections {
target: styleController
onCurrentStyleChanged: {
var popup = styleChangedDialog.createObject(root)
popup.open()
}
}
}
RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins text: "Style"
Layout.rightMargin: app.margins }
Label { ComboBox {
Layout.fillWidth: true model: styleController.allStyles
text: qsTr("Unit system") currentIndex: styleController.allStyles.indexOf(styleController.currentStyle)
}
ComboBox { onActivated: {
id: unitsComboBox styleController.currentStyle = model[index]
currentIndex: settings.units === "metric" ? 0 : 1
model: [ qsTr("Metric"), qsTr("Imperial") ]
onActivated: {
settings.units = index == 0 ? "metric" : "imperial";
}
} }
} }
CheckDelegate { Connections {
Layout.fillWidth: true target: styleController
text: qsTr("Return to home on idle") onCurrentStyleChanged: {
checked: settings.returnToHome var popup = styleChangedDialog.createObject(root)
onClicked: settings.returnToHome = checked popup.open()
}
CheckDelegate {
Layout.fillWidth: true
text: qsTr("Show connection tabs")
checked: settings.showConnectionTabs
onClicked: settings.showConnectionTabs = checked
}
CheckDelegate {
id: screenOffCheck
Layout.fillWidth: true
text: qsTr("Turn screen off when idle")
visible: PlatformHelper.canControlScreen
checked: PlatformHelper.screenTimeout > 0
onClicked: PlatformHelper.screenTimeout = (checked ? 15000 : 0)
}
ItemDelegate {
Layout.fillWidth: true
Layout.preferredHeight: screenOffCheck.height
visible: PlatformHelper.screenTimeout > 0
topPadding: 0
contentItem: RowLayout {
Label {
Layout.fillWidth: true
text: qsTr("Screen off timeout")
}
SpinBox {
value: PlatformHelper.screenTimeout / 1000
onValueModified: {
PlatformHelper.screenTimeout = value * 1000
}
}
Label {
text: qsTr("seconds")
}
} }
} }
}
ItemDelegate { RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: !kioskMode && Qt.platform.os !== "ios"
Label {
Layout.fillWidth: true Layout.fillWidth: true
visible: PlatformHelper.canControlScreen text: qsTr("View mode")
topPadding: 0 }
contentItem: RowLayout { ComboBox {
Label { model: [qsTr("Windowed"), qsTr("Maximized"), qsTr("Fullscreen"), qsTr("Automatic")]
Layout.fillWidth: true currentIndex: {
text: qsTr("Screen brightness") switch (settings.viewMode) {
case ApplicationWindow.Windowed:
return 0;
case ApplicationWindow.Maximized:
return 1;
case ApplicationWindow.FullScreen:
return 2;
case ApplicationWindow.AutomaticVisibility:
return 3;
} }
Slider { }
Layout.fillWidth: true
value: PlatformHelper.screenBrightness onActivated: {
onMoved: PlatformHelper.screenBrightness = value switch (currentIndex) {
from: 0 case 0:
to: 100 settings.viewMode = ApplicationWindow.Windowed;
stepSize: 1 break;
case 1:
settings.viewMode = ApplicationWindow.Maximized;
break;
case 2:
settings.viewMode = ApplicationWindow.FullScreen;
break;
case 3:
settings.viewMode = ApplicationWindow.AutomaticVisibility;
break;
} }
} }
} }
} }
CheckDelegate {
Layout.fillWidth: true
text: qsTr("Show connection tabs")
checked: settings.showConnectionTabs
onClicked: settings.showConnectionTabs = checked
}
RowLayout {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: settings.showHiddenOptions
Label {
Layout.fillWidth: true
text: qsTr("Experience mode")
}
ComboBox {
currentIndex: model.indexOf(styleController.currentExperience)
model: styleController.allExperiences
onActivated: {
styleController.currentExperience = model[index]
}
}
}
SettingsPageSectionHeader {
text: qsTr("Regional")
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Label {
Layout.fillWidth: true
text: qsTr("Unit system")
}
ComboBox {
id: unitsComboBox
currentIndex: settings.units === "metric" ? 0 : 1
model: [ qsTr("Metric"), qsTr("Imperial") ]
onActivated: {
settings.units = index == 0 ? "metric" : "imperial";
}
}
}
SettingsPageSectionHeader {
text: qsTr("Behavior")
}
CheckDelegate {
Layout.fillWidth: true
text: qsTr("Return to home on idle")
checked: settings.returnToHome
onClicked: settings.returnToHome = checked
}
CheckDelegate {
id: screenOffCheck
Layout.fillWidth: true
text: qsTr("Turn screen off when idle")
visible: PlatformHelper.canControlScreen
checked: PlatformHelper.screenTimeout > 0
onClicked: PlatformHelper.screenTimeout = (checked ? 15000 : 0)
}
ItemDelegate {
Layout.fillWidth: true
Layout.preferredHeight: screenOffCheck.height
visible: PlatformHelper.screenTimeout > 0
topPadding: 0
contentItem: RowLayout {
Label {
Layout.fillWidth: true
text: qsTr("Screen off timeout")
}
SpinBox {
value: PlatformHelper.screenTimeout / 1000
onValueModified: {
PlatformHelper.screenTimeout = value * 1000
}
}
Label {
text: qsTr("seconds")
}
}
}
ItemDelegate {
Layout.fillWidth: true
visible: PlatformHelper.canControlScreen
topPadding: 0
contentItem: RowLayout {
Label {
Layout.fillWidth: true
text: qsTr("Screen brightness")
}
Slider {
Layout.fillWidth: true
value: PlatformHelper.screenBrightness
onMoved: PlatformHelper.screenBrightness = value
from: 0
to: 100
stepSize: 1
}
}
}
Component { Component {
id: styleChangedDialog id: styleChangedDialog
Dialog { Dialog {

View File

@ -99,7 +99,7 @@ ColumnLayout {
} }
var ret = [] var ret = []
for (var i = 0; i < texts.length; i++) { for (var i = 0; i < texts.length; i++) {
var entry = "<font color=\"%1\">• ".arg(checks[i] ? app.foregroundColor : app.accentColor) var entry = "<font color=\"%1\">• ".arg(checks[i] ? "#ffffff" : app.accentColor)
entry += texts[i] entry += texts[i]
entry += "</font>" entry += "</font>"
ret.push(entry) ret.push(entry)

View File

@ -0,0 +1,65 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.1
import Nymea 1.0
Page {
id: root
header: NymeaHeader {
text: root.title
backButtonVisible: true
onBackPressed: pageStack.pop()
}
property alias busy: busyOverlay.shown
default property alias content: contentColumn.data
Flickable {
anchors.fill: parent
contentHeight: contentColumn.height
interactive: contentHeight > height
ScrollBar.vertical: ScrollBar {}
ColumnLayout {
id: contentColumn
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(500, parent.width)
}
}
BusyOverlay {
id: busyOverlay
}
}

View File

@ -0,0 +1,43 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
Label {
Layout.fillWidth: true
Layout.topMargin: app.margins * 1.5
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.bottomMargin: app.margins
color: app.accentColor
wrapMode: Text.WordWrap
}

View File

@ -53,7 +53,7 @@ Page {
popup.open(); popup.open();
} }
onCreateUserSucceeded: { onCreateUserSucceeded: {
engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app"); engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app (" + PlatformHelper.deviceModel + ")");
} }
onCreateUserFailed: { onCreateUserFailed: {
@ -153,7 +153,7 @@ Page {
engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.password); engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.password);
} else { } else {
print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app") print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app")
engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app"); engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app (" + PlatformHelper.deviceModel + ")");
} }
} }
} }

170
nymea-app/ui/images/key.svg Normal file
View File

@ -0,0 +1,170 @@
<?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 r13725"
viewBox="0 0 96 96.000001"
sodipodi:docname="stock_key01.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8.7812489"
inkscape:cx="35.148755"
inkscape:cy="15.789312"
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:snap-global="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="45,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="-8,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-8,8.0000001"
id="guide4073" />
<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="1,0"
position="84,-8.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:guide
position="92,-8.0000001"
orientation="1,0"
id="guide4760" />
<sodipodi:guide
position="-8,12"
orientation="0,1"
id="guide3234" />
</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 />
</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">
<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">
<rect
style="color:#000000;fill:none;stroke-width:4"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;solid-opacity:1;fill:#808080;stroke-width:4.00079155"
d="M 35.976562 0 C 30.943803 0.058176982 27.26124 -0.11846389 24.25 1.5429688 C 22.74438 2.3736801 21.555084 3.7767542 20.896484 5.53125 C 20.237874 7.2857458 20 9.3696407 20 12 L 20 28 C 20 30.630359 20.237874 32.712301 20.896484 34.466797 C 21.555084 36.221293 22.74438 37.624367 24.25 38.455078 C 27.26124 40.116501 30.943792 39.93987 35.976562 39.998047 L 35.988281 40 L 37.998047 40 L 37.998047 72.828125 L 43.169922 78 L 37.169922 84 L 47.169922 94 L 58 94 L 58 40 L 60.011719 40 L 60.021484 39.998047 C 65.054254 39.93987 68.736807 40.116501 71.748047 38.455078 C 73.253667 37.624367 74.444916 36.221293 75.103516 34.466797 C 75.762116 32.712301 76 30.630359 76 28 L 76 12 C 76 9.3696407 75.762116 7.2857458 75.103516 5.53125 C 74.444916 3.7767542 73.253667 2.3736801 71.748047 1.5429688 C 68.736807 -0.11846389 65.054244 0.058176982 60.021484 0 L 60.011719 0 L 35.988281 0 L 35.976562 0 z M 36.023438 3.9980469 L 60 3.9980469 C 65.03826 4.0568136 68.350173 4.2350584 69.814453 5.0429688 C 70.547973 5.4476786 70.967112 5.8977214 71.357422 6.9375 C 71.747742 7.9772786 71.998047 9.6303576 71.998047 12 L 71.998047 28 C 71.998047 30.369642 71.747742 32.022711 71.357422 33.0625 C 70.967112 34.102279 70.547973 34.550368 69.814453 34.955078 C 68.350173 35.762988 65.03825 35.941223 60 36 L 59.976562 36 L 58 36 L 55.998047 36 L 53.998047 36 L 53.998047 90 L 48.828125 90 L 42.828125 84 L 48.828125 78 L 42 71.171875 L 42 36 L 40 36 L 37.998047 36 L 36.023438 36 L 36 36 C 30.96174 35.941233 27.647884 35.762988 26.183594 34.955078 C 25.450074 34.550368 25.032888 34.102279 24.642578 33.0625 C 24.252268 32.022711 24 30.369642 24 28 L 24 12 C 24 9.6303576 24.252268 7.9772786 24.642578 6.9375 C 25.032888 5.8977214 25.450074 5.4476786 26.183594 5.0429688 C 27.650624 4.233549 30.969268 4.0565237 36.023438 3.9980469 z "
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
id="path4643" />
<ellipse
style="color:#000000;fill:#808080;stroke-width:4.00079155;stroke-dashoffset:0.25999999"
id="path3281"
transform="matrix(0,-1,-1,0,0,0)"
cx="-393.36221"
cy="-423.99692"
rx="5.9999971"
ry="6.0023713" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -34,60 +34,52 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("About %1:core").arg(app.systemName)
text: qsTr("About %1:core").arg(app.systemName)
onBackPressed: pageStack.pop()
}
Flickable {
anchors.fill: parent
contentHeight: imprint.implicitHeight
Imprint { Imprint {
id: imprint id: imprint
width: parent.width Layout.fillWidth: true
title: qsTr("%1:core").arg(app.systemName) title: qsTr("%1:core").arg(app.systemName)
githubLink: "https://github.com/nymea/nymea" githubLink: "https://github.com/nymea/nymea"
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Connection:") text: qsTr("Connection:")
subText: engine.connection.currentConnection.url subText: engine.connection.currentConnection.url
progressive: false progressive: false
prominentSubText: false prominentSubText: false
} }
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Server UUID:") text: qsTr("Server UUID:")
subText: engine.jsonRpcClient.serverUuid subText: engine.jsonRpcClient.serverUuid
progressive: false progressive: false
prominentSubText: false prominentSubText: false
} }
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Server version:") text: qsTr("Server version:")
subText: engine.jsonRpcClient.serverVersion subText: engine.jsonRpcClient.serverVersion
progressive: false progressive: false
prominentSubText: false prominentSubText: false
} }
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("JSON-RPC version:") text: qsTr("JSON-RPC version:")
subText: engine.jsonRpcClient.jsonRpcVersion subText: engine.jsonRpcClient.jsonRpcVersion
progressive: false progressive: false
prominentSubText: false prominentSubText: false
} }
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Qt version:") text: qsTr("Qt version:")
visible: engine.jsonRpcClient.ensureServerVersion("4.1") visible: engine.jsonRpcClient.ensureServerVersion("4.1")
subText: engine.jsonRpcClient.serverQtVersion + (engine.jsonRpcClient.serverQtVersion !== engine.jsonRpcClient.serverQtBuildVersion ? + " (" + qsTr("Built with %1").arg(engine.jsonRpcClient.serverQtBuildVersion) + ")" : "") subText: engine.jsonRpcClient.serverQtVersion + (engine.jsonRpcClient.serverQtVersion !== engine.jsonRpcClient.serverQtBuildVersion ? + " (" + qsTr("Built with %1").arg(engine.jsonRpcClient.serverQtBuildVersion) + ")" : "")
progressive: false progressive: false
prominentSubText: false prominentSubText: false
}
} }
} }
} }

View File

@ -34,12 +34,9 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("%1:core cloud settings").arg(app.systemName)
text: qsTr("Cloud settings")
onBackPressed: pageStack.pop();
}
Item { Item {
id: d id: d
@ -59,97 +56,120 @@ Page {
} }
} }
ColumnLayout { SettingsPageSectionHeader {
anchors { left: parent.left; top: parent.top; right: parent.right } text: qsTr("Cloud connection")
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Connect %1:core to %1:cloud in order to access it from anywhere and send push notifications from %1:core to %2.").arg(app.systemName).arg(app.appName)
wrapMode: Text.WordWrap
}
// Button {
// text: "pair"
// onClicked: engine.jsonRpcClient.setupRemoteAccess(AWSClient.idToken, AWSClient.userId)
// }
SwitchDelegate {
Layout.fillWidth: true
text: qsTr("Cloud connection enabled")
checked: engine.nymeaConfiguration.cloudEnabled
onToggled: {
engine.nymeaConfiguration.cloudEnabled = checked;
}
}
SettingsPageSectionHeader {
text: qsTr("Status")
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
ColorIcon {
Layout.preferredHeight: busyIndicator.height
Layout.preferredWidth: height
name: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateConnected
? "../images/cloud.svg"
: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured
? "../images/cloud-error.svg"
: "../images/cloud-offline.svg"
}
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.topMargin: app.margins
text: qsTr("You can connect a nymea:box to a nymea:cloud in order to access it from anywhere")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} text: {
switch (engine.jsonRpcClient.cloudConnectionState) {
// Button { case JsonRpcClient.CloudConnectionStateDisabled:
// text: "pair" return qsTr("This box is not connected to %1:cloud").arg(app.systemName)
// onClicked: engine.jsonRpcClient.setupRemoteAccess(AWSClient.idToken, AWSClient.userId) case JsonRpcClient.CloudConnectionStateUnconfigured:
// } if (d.deploymentStarted) {
return qsTr("Registering box in %1:cloud...").arg(app.systemName)
SwitchDelegate {
Layout.fillWidth: true
text: qsTr("Cloud connection enabled")
checked: engine.nymeaConfiguration.cloudEnabled
onToggled: {
engine.nymeaConfiguration.cloudEnabled = checked;
}
}
ThinDivider {}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
ColorIcon {
Layout.preferredHeight: busyIndicator.height
Layout.preferredWidth: height
name: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateConnected
? "../images/cloud.svg"
: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured
? "../images/cloud-error.svg"
: "../images/cloud-offline.svg"
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: {
switch (engine.jsonRpcClient.cloudConnectionState) {
case JsonRpcClient.CloudConnectionStateDisabled:
return qsTr("This box is not connected to %1:cloud").arg(app.systemName)
case JsonRpcClient.CloudConnectionStateUnconfigured:
if (d.deploymentStarted) {
return qsTr("Registering box in %1:cloud...").arg(app.systemName)
}
return qsTr("This box is not configured to connect to %1:cloud.").arg(app.systemName);
case JsonRpcClient.CloudConnectionStateConnecting:
return qsTr("Connecting the box to %1:cloud...").arg(app.systemName);
case JsonRpcClient.CloudConnectionStateConnected:
return qsTr("The box is connected to %1:cloud.").arg(app.systemName);
} }
return engine.jsonRpcClient.cloudConnectionState return qsTr("This box is not configured to connect to %1:cloud.").arg(app.systemName);
case JsonRpcClient.CloudConnectionStateConnecting:
return qsTr("Connecting the box to %1:cloud...").arg(app.systemName);
case JsonRpcClient.CloudConnectionStateConnected:
return qsTr("The box is connected to %1:cloud.").arg(app.systemName);
} }
} return engine.jsonRpcClient.cloudConnectionState
BusyIndicator {
id: busyIndicator
visible: (engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateUnconfigured && d.deploymentStarted) ||
engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateConnecting
} }
} }
BusyIndicator {
Label { id: busyIndicator
Layout.fillWidth: true visible: (engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateUnconfigured && d.deploymentStarted) ||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateConnecting
visible: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured && !d.deploymentStarted
text: qsTr("This box is not configured to access the %1:cloud. In order for a box to connect to %1:cloud it needs to be registered first.").arg(app.systemName)
wrapMode: Text.WordWrap
} }
}
Button { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
visible: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured && !d.deploymentStarted visible: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured && !d.deploymentStarted
text: AWSClient.isLoggedIn ? qsTr("Register box") : qsTr("Log in to cloud") text: qsTr("This box is not configured to access the %1:cloud. In order for a box to connect to %1:cloud it needs to be registered first.").arg(app.systemName)
onClicked: { wrapMode: Text.WordWrap
if (AWSClient.isLoggedIn) { }
d.deploymentStarted = true
engine.deployCertificate(); Button {
} else { Layout.fillWidth: true
pageStack.push(Qt.resolvedUrl("qrc:/ui/appsettings/CloudLoginPage.qml")) Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
} visible: engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured && !d.deploymentStarted
text: AWSClient.isLoggedIn ? qsTr("Register box") : qsTr("Log in to cloud")
onClicked: {
if (AWSClient.isLoggedIn) {
d.deploymentStarted = true
engine.deployCertificate();
} else {
pageStack.push(Qt.resolvedUrl("qrc:/ui/appsettings/CloudLoginPage.qml"))
} }
} }
} }
SettingsPageSectionHeader {
text: qsTr("Remote connection")
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("In order to remotely connect to this %1:core, %2 needs to be logged into %1:cloud as well.").arg(app.systemName).arg(app.appName)
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Go to app settings")
subText: qsTr("Set up cloud connection for %1").arg(app.appName)
prominentSubText: false
onClicked: {
pageStack.push(Qt.resolvedUrl("../appsettings/CloudLoginPage.qml"))
}
}
} }

View File

@ -34,127 +34,101 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("Connection interfaces")
text: qsTr("Connection interfaces")
onBackPressed: pageStack.pop();
SettingsPageSectionHeader {
text: qsTr("TCP server interfaces")
} }
Flickable { Repeater {
anchors.fill: parent model: engine.nymeaConfiguration.tcpServerConfigurations
contentHeight: connectionsColumn.implicitHeight delegate: ConnectionInterfaceDelegate {
interactive: contentHeight > height Layout.fillWidth: true
canDelete: true
ColumnLayout { onClicked: {
id: connectionsColumn var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
anchors { left: parent.left; top: parent.top; right: parent.right } var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.tcpServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
Label { engine.nymeaConfiguration.setTcpServerConfiguration(popup.serverConfiguration)
Layout.fillWidth: true popup.serverConfiguration.destroy();
Layout.leftMargin: app.margins })
Layout.rightMargin: app.margins popup.rejected.connect(function() {
Layout.topMargin: app.margins popup.serverConfiguration.destroy();
text: qsTr("TCP server interfaces") })
wrapMode: Text.WordWrap popup.open()
color: app.accentColor
} }
onDeleteClicked: {
Repeater { print("should delete")
model: engine.nymeaConfiguration.tcpServerConfigurations engine.nymeaConfiguration.deleteTcpServerConfiguration(model.id)
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
canDelete: true
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.tcpServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setTcpServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
onDeleteClicked: {
print("should delete")
engine.nymeaConfiguration.deleteTcpServerConfiguration(model.id)
}
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createServerConfiguration("0.0.0.0", 2222 + engine.nymeaConfiguration.tcpServerConfigurations.count, false, false);
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setTcpServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.topMargin: app.margins
text: qsTr("WebSocket server interfaces")
wrapMode: Text.WordWrap
color: app.accentColor
}
Repeater {
model: engine.nymeaConfiguration.webSocketServerConfigurations
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
canDelete: true
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.webSocketServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
print("configuring:", popup.serverConfiguration.port)
engine.nymeaConfiguration.setWebSocketServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
onDeleteClicked: {
print("should delete", model.id)
engine.nymeaConfiguration.deleteWebSocketServerConfiguration(model.id)
}
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createServerConfiguration("0.0.0.0", 4444 + engine.nymeaConfiguration.webSocketServerConfigurations.count, false, false);
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setWebSocketServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
} }
} }
} }
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createServerConfiguration("0.0.0.0", 2222 + engine.nymeaConfiguration.tcpServerConfigurations.count, false, false);
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setTcpServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
}
SettingsPageSectionHeader {
text: qsTr("WebSocket server interfaces")
}
Repeater {
model: engine.nymeaConfiguration.webSocketServerConfigurations
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
canDelete: true
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.webSocketServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
print("configuring:", popup.serverConfiguration.port)
engine.nymeaConfiguration.setWebSocketServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
onDeleteClicked: {
print("should delete", model.id)
engine.nymeaConfiguration.deleteWebSocketServerConfiguration(model.id)
}
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createServerConfiguration("0.0.0.0", 4444 + engine.nymeaConfiguration.webSocketServerConfigurations.count, false, false);
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setWebSocketServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
}
} }

View File

@ -34,12 +34,9 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("Developer tools")
text: qsTr("Developer tools")
onBackPressed: pageStack.pop();
}
property WebServerConfiguration usedConfig: { property WebServerConfiguration usedConfig: {
var config = null var config = null
@ -49,7 +46,7 @@ Page {
if (tmp.address === engine.connection.currentConnection.hostAddress || tmp.address === "0.0.0.0") { if (tmp.address === engine.connection.currentConnection.hostAddress || tmp.address === "0.0.0.0") {
// This one prefers https over http... // This one prefers https over http...
// if (config === null || (!config.sslEnabled && tmp.sslEnabled)) { // if (config === null || (!config.sslEnabled && tmp.sslEnabled)) {
// ...but for now, prefer http because self signed certs cause trouble and this is meant for local debugging only anyways... // ...but for now, prefer http because self signed certs cause trouble and this is meant for local debugging only anyways...
if (config === null || (config.sslEnabled && !tmp.sslEnabled)) { if (config === null || (config.sslEnabled && !tmp.sslEnabled)) {
@ -61,72 +58,66 @@ Page {
return config; return config;
} }
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
RowLayout { SettingsPageSectionHeader {
Layout.fillWidth: true text: qsTr("Debug server")
Layout.leftMargin: app.margins }
Layout.rightMargin: app.margins
spacing: app.margins
Label {
text: qsTr("Debug server enabled")
Layout.fillWidth: true
}
Switch {
id: debugServerEnabledSwitch
checked: engine.nymeaConfiguration.debugServerEnabled
onClicked: engine.nymeaConfiguration.debugServerEnabled = checked
}
}
Label { SwitchDelegate {
Layout.fillWidth: true id: debugServerEnabledSwitch
Layout.leftMargin: app.margins Layout.fillWidth: true
Layout.rightMargin: app.margins text: qsTr("Debug server enabled")
text: qsTr("In order to access the debug interface, please enable the web server.") checked: engine.nymeaConfiguration.debugServerEnabled
font.pixelSize: app.smallFont onToggled: engine.nymeaConfiguration.debugServerEnabled = checked
color: "red" }
wrapMode: Text.WordWrap
visible: engine.nymeaConfiguration.webServerConfigurations.count === 0
}
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins Layout.leftMargin: app.margins
Layout.rightMargin: app.margins Layout.rightMargin: app.margins
text: qsTr("The web server cannot be reached on %1.").arg(engine.connection.currentConnection.hostAddress) text: qsTr("In order to access the debug interface, please enable the web server.")
wrapMode: Text.WordWrap font.pixelSize: app.smallFont
font.pixelSize: app.smallFont color: "red"
color: "red" wrapMode: Text.WordWrap
visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null visible: engine.nymeaConfiguration.webServerConfigurations.count === 0
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: app.margins Layout.leftMargin: app.margins
Layout.rightMargin: app.margins Layout.rightMargin: app.margins
text: qsTr("Please enable the web server to be accessed on this address.") text: qsTr("The web server cannot be reached on %1.").arg(engine.connection.currentConnection.hostAddress)
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: app.smallFont font.pixelSize: app.smallFont
visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null color: "red"
} visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null
}
Button { Label {
id: debugServerButton Layout.fillWidth: true
Layout.fillWidth: true Layout.leftMargin: app.margins
Layout.margins: app.margins Layout.rightMargin: app.margins
visible: debugServerEnabledSwitch.checked text: qsTr("Please enable the web server to be accessed on this address.")
enabled: root.usedConfig !== null && engine.nymeaConfiguration.webServerConfigurations.count > 0 wrapMode: Text.WordWrap
text: qsTr("Open debug interface") font.pixelSize: app.smallFont
onClicked: { visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null
print("opening:", engine.connection.currentConnection.url) }
var proto = "http" + (root.usedConfig.sslEnabled ? "s" : "") + "://" Button {
var path = engine.connection.currentConnection.hostAddress + ":" + root.usedConfig.port + "/debug" id: debugServerButton
print("opening:", proto + path) Layout.fillWidth: true
Qt.openUrlExternally(proto + path) Layout.margins: app.margins
} visible: debugServerEnabledSwitch.checked
enabled: root.usedConfig !== null && engine.nymeaConfiguration.webServerConfigurations.count > 0
text: qsTr("Open debug interface")
onClicked: {
print("opening:", engine.connection.currentConnection.url)
var proto = "http" + (root.usedConfig.sslEnabled ? "s" : "") + "://"
var path = engine.connection.currentConnection.hostAddress + ":" + root.usedConfig.port + "/debug"
print("opening:", proto + path)
Qt.openUrlExternally(proto + path)
} }
} }
} }

View File

@ -35,192 +35,213 @@ import QtQuick.Layouts 1.1
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("General settings")
text: qsTr("General settings")
backButtonVisible: true
onBackPressed: pageStack.pop() SettingsPageSectionHeader {
text: qsTr("General")
} }
ColumnLayout { RowLayout {
id: settingsGrid Layout.fillWidth: true
anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; margins: app.margins } Layout.leftMargin: app.margins
width: Math.min(500, parent.width - app.margins * 2) Layout.rightMargin: app.margins
spacing: app.margins
RowLayout { Label {
text: qsTr("Name")
Layout.fillWidth: true Layout.fillWidth: true
spacing: app.margins
Label {
text: qsTr("Name")
}
TextField {
id: nameTextField
Layout.fillWidth: true
text: engine.nymeaConfiguration.serverName
}
Button {
text: qsTr("OK")
visible: nameTextField.displayText !== engine.nymeaConfiguration.serverName
onClicked: engine.nymeaConfiguration.serverName = nameTextField.displayText
}
} }
TextField {
RowLayout { id: nameTextField
Layout.fillWidth: true Layout.fillWidth: true
visible: engine.jsonRpcClient.ensureServerVersion("4.1") && engine.systemController.automaticTimeAvailable text: engine.nymeaConfiguration.serverName
Label {
text: qsTr("Set date and time automatically")
Layout.fillWidth: true
}
CheckBox {
checked: engine.systemController.automaticTime
onClicked: {
engine.systemController.automaticTime = checked
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: app.margins
Layout.preferredHeight: dateButton.implicitHeight
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
Label {
text: qsTr("Date")
Layout.fillWidth: true
}
Label {
text: engine.systemController.serverTime.toLocaleDateString()
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
Button {
id: dateButton
visible: !engine.systemController.automaticTime && engine.systemController.timeManagementAvailable
contentItem: Item {
ColorIcon {
name: "../images/edit.svg"
color: app.foregroundColor
anchors.centerIn: parent
height: parent.height
width: height
}
}
onClicked: {
var popup = datePickerComponent.createObject(root, {dateTime: engine.systemController.serverTime})
popup.accepted.connect(function() {
print("setting new date", popup.dateTime)
engine.systemController.serverTime = popup.dateTime
})
popup.open();
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: app.margins
Layout.preferredHeight: timeButton.implicitHeight
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
Label {
text: qsTr("Time")
Layout.fillWidth: true
}
Label {
text: engine.systemController.serverTime.toLocaleTimeString(/*Locale.ShortTimeString*/)
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
Button {
id: timeButton
visible: !engine.systemController.automaticTime && engine.systemController.timeManagementAvailable
contentItem: Item {
ColorIcon {
name: "../images/edit.svg"
color: app.foregroundColor
anchors.centerIn: parent
height: parent.height
width: height
}
}
onClicked: {
var popup = timePickerComponent.createObject(root, {hour: engine.systemController.serverTime.getHours(), minute: engine.systemController.serverTime.getMinutes()})
popup.accepted.connect(function() {
var date = new Date(engine.systemController.serverTime)
date.setHours(popup.hour);
date.setMinutes(popup.minute)
engine.systemController.serverTime = date;
})
popup.open();
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: app.margins
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
Label {
Layout.fillWidth: true
text: qsTr("Time zone")
}
ComboBox {
Layout.minimumWidth: 200
model: engine.systemController.timeZones
currentIndex: model.indexOf(engine.systemController.serverTimeZone)
onActivated: {
engine.systemController.serverTimeZone = currentText;
}
}
}
Button {
Layout.fillWidth: true
text: qsTr("Reboot %1:core").arg(app.systemName)
visible: engine.systemController.powerManagementAvailable
onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Are you sure you want to reboot your %1:core sytem now?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Reboot %1:core").arg(app.systemName),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.reboot()
})
}
} }
Button { Button {
text: qsTr("OK")
visible: nameTextField.displayText !== engine.nymeaConfiguration.serverName
onClicked: engine.nymeaConfiguration.serverName = nameTextField.displayText
}
}
SettingsPageSectionHeader {
text: qsTr("Date and time")
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: engine.jsonRpcClient.ensureServerVersion("4.1") && engine.systemController.automaticTimeAvailable
Label {
text: qsTr("Set date and time automatically")
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Shutdown %1:core").arg(app.systemName) }
visible: engine.systemController.powerManagementAvailable CheckBox {
checked: engine.systemController.automaticTime
onClicked: { onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); engine.systemController.automaticTime = checked
var text = qsTr("Are you sure you want to shut down your %1:core sytem now?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Shut down %1:core").arg(app.systemName),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.shutdown()
})
} }
} }
} }
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
spacing: app.margins
Layout.preferredHeight: dateButton.implicitHeight
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
Label {
text: qsTr("Date")
Layout.fillWidth: true
}
Label {
text: engine.systemController.serverTime.toLocaleDateString()
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
Button {
id: dateButton
visible: !engine.systemController.automaticTime && engine.systemController.timeManagementAvailable
contentItem: Item {
ColorIcon {
name: "../images/edit.svg"
color: app.foregroundColor
anchors.centerIn: parent
height: parent.height
width: height
}
}
onClicked: {
var popup = datePickerComponent.createObject(root, {dateTime: engine.systemController.serverTime})
popup.accepted.connect(function() {
print("setting new date", popup.dateTime)
engine.systemController.serverTime = popup.dateTime
})
popup.open();
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
spacing: app.margins
Layout.preferredHeight: timeButton.implicitHeight
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
Label {
text: qsTr("Time")
Layout.fillWidth: true
}
Label {
text: engine.systemController.serverTime.toLocaleTimeString(/*Locale.ShortTimeString*/)
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
Button {
id: timeButton
visible: !engine.systemController.automaticTime && engine.systemController.timeManagementAvailable
contentItem: Item {
ColorIcon {
name: "../images/edit.svg"
color: app.foregroundColor
anchors.centerIn: parent
height: parent.height
width: height
}
}
onClicked: {
var popup = timePickerComponent.createObject(root, {hour: engine.systemController.serverTime.getHours(), minute: engine.systemController.serverTime.getMinutes()})
popup.accepted.connect(function() {
var date = new Date(engine.systemController.serverTime)
date.setHours(popup.hour);
date.setMinutes(popup.minute)
engine.systemController.serverTime = date;
})
popup.open();
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
spacing: app.margins
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
Label {
Layout.fillWidth: true
text: qsTr("Time zone")
}
ComboBox {
Layout.minimumWidth: 200
model: engine.systemController.timeZones
currentIndex: model.indexOf(engine.systemController.serverTimeZone)
onActivated: {
engine.systemController.serverTimeZone = currentText;
}
}
}
SettingsPageSectionHeader {
text: qsTr("System")
visible: engine.systemController.powerManagementAvailable
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Reboot %1:core").arg(app.systemName)
visible: engine.systemController.powerManagementAvailable
onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Are you sure you want to reboot your %1:core sytem now?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Reboot %1:core").arg(app.systemName),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.reboot()
})
}
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Shutdown %1:core").arg(app.systemName)
visible: engine.systemController.powerManagementAvailable
onClicked: {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Are you sure you want to shut down your %1:core sytem now?").arg(app.systemName)
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Shut down %1:core").arg(app.systemName),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
engine.systemController.shutdown()
})
}
}
Component { Component {
id: timePickerComponent id: timePickerComponent
Dialog { Dialog {

View File

@ -34,140 +34,102 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("MQTT broker")
text: qsTr("MQTT broker")
onBackPressed: pageStack.pop(); SettingsPageSectionHeader {
text: qsTr("MQTT Server Interfaces")
} }
// Flickable { Repeater {
// anchors.fill: parent model: engine.nymeaConfiguration.mqttServerConfigurations
// contentHeight: connectionsColumn.implicitHeight
// interactive: contentHeight > height
ColumnLayout { delegate: ConnectionInterfaceDelegate {
id: connectionsColumn Layout.fillWidth: true
// anchors { left: parent.left; top: parent.top; right: parent.right } canDelete: true
anchors.fill: parent onClicked: {
// layoutDirection: Qt. var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
Label { var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.mqttServerConfigurations.get(index).clone() });
Layout.fillWidth: true popup.accepted.connect(function() {
Layout.leftMargin: app.margins engine.nymeaConfiguration.setMqttServerConfiguration(popup.serverConfiguration)
Layout.rightMargin: app.margins popup.serverConfiguration.destroy();
Layout.topMargin: app.margins })
text: qsTr("MQTT Server Interfaces") popup.rejected.connect(function() {
wrapMode: Text.WordWrap popup.serverConfiguration.destroy();
color: app.accentColor })
popup.open()
} }
ListView { onDeleteClicked: {
Layout.fillWidth: true engine.nymeaConfiguration.deleteMqttServerConfiguration(model.id)
Layout.minimumHeight: 0
Layout.preferredHeight: Math.min(contentHeight, 120)
model: engine.nymeaConfiguration.mqttServerConfigurations
clip: true
ScrollBar.vertical: ScrollBar {}
delegate: ConnectionInterfaceDelegate {
width: parent.width
canDelete: true
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.mqttServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setMqttServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
onDeleteClicked: {
engine.nymeaConfiguration.deleteMqttServerConfiguration(model.id)
}
}
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createServerConfiguration("0.0.0.0", 1883 + engine.nymeaConfiguration.mqttServerConfigurations.count, false, false);
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setMqttServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.topMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("MQTT permissions")
wrapMode: Text.WordWrap
color: app.accentColor
}
ListView {
Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, parent.height * .4)
model: engine.nymeaConfiguration.mqttPolicies
clip: true
ScrollBar.vertical: ScrollBar {}
delegate: NymeaListItemDelegate {
width: parent.width
iconName: "../images/account.svg"
text: qsTr("Client ID: %1").arg(model.clientId)
subText: qsTr("Username: %1").arg(model.username)
progressive: false
canDelete: true
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("MqttPolicyPage.qml"), { policy: engine.nymeaConfiguration.mqttPolicies.get(index).clone() });
page.accepted.connect(function() {
if (page.policy.clientId !== model.clientId) {
engine.nymeaConfiguration.deleteMqttPolicy(model.clientId);
}
engine.nymeaConfiguration.updateMqttPolicy(page.policy)
page.policy.destroy();
})
page.rejected.connect(function() {
page.policy.destroy();
})
}
onDeleteClicked: {
engine.nymeaConfiguration.deleteMqttPolicy(model.clientId)
}
}
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Add")
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("MqttPolicyPage.qml"), { policy: engine.nymeaConfiguration.createMqttPolicy() });
page.accepted.connect(function() {
engine.nymeaConfiguration.updateMqttPolicy(page.policy)
page.policy.destroy();
})
page.rejected.connect(function() {
page.policy.destroy();
})
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 0
} }
} }
// } }
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createServerConfiguration("0.0.0.0", 1883 + engine.nymeaConfiguration.mqttServerConfigurations.count, false, false);
var component = Qt.createComponent(Qt.resolvedUrl("ServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setMqttServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
}
SettingsPageSectionHeader {
text: qsTr("MQTT permissions")
}
Repeater {
model: engine.nymeaConfiguration.mqttPolicies
delegate: NymeaListItemDelegate {
Layout.fillWidth: true
iconName: "../images/account.svg"
text: qsTr("Client ID: %1").arg(model.clientId)
subText: qsTr("Username: %1").arg(model.username)
progressive: false
canDelete: true
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("MqttPolicyPage.qml"), { policy: engine.nymeaConfiguration.mqttPolicies.get(index).clone() });
page.accepted.connect(function() {
if (page.policy.clientId !== model.clientId) {
engine.nymeaConfiguration.deleteMqttPolicy(model.clientId);
}
engine.nymeaConfiguration.updateMqttPolicy(page.policy)
page.policy.destroy();
})
page.rejected.connect(function() {
page.policy.destroy();
})
}
onDeleteClicked: {
engine.nymeaConfiguration.deleteMqttPolicy(model.clientId)
}
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var page = pageStack.push(Qt.resolvedUrl("MqttPolicyPage.qml"), { policy: engine.nymeaConfiguration.createMqttPolicy() });
page.accepted.connect(function() {
engine.nymeaConfiguration.updateMqttPolicy(page.policy)
page.policy.destroy();
})
page.rejected.connect(function() {
page.policy.destroy();
})
}
}
} }

View File

@ -35,7 +35,7 @@ import QtQuick.Layouts 1.1
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { header: NymeaHeader {
text: qsTr("Mqtt permission") text: qsTr("Mqtt permission")
@ -57,175 +57,176 @@ Page {
signal accepted(); signal accepted();
signal rejected() signal rejected()
ColumnLayout { Label {
anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom } Layout.fillWidth: true
RowLayout { Layout.margins: app.margins
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins text: qsTr("Client info")
spacing: app.margins color: app.accentColor
Label { }
text: qsTr("Client ID:")
Layout.fillWidth: true
}
TextField {
id: clientIdTextField
Layout.fillWidth: true
text: root.policy ? root.policy.clientId : ""
onEditingFinished: root.policy.clientId = text
placeholderText: qsTr("E.g. Sensor_1")
property bool isEmpty: displayText.length === 0
property bool isDuplicate: clientIdTextField.displayText != root.policy.clientId && engine.nymeaConfiguration.mqttPolicies.getPolicy(clientIdTextField.displayText) !== null
property bool isValid: !isEmpty && !isDuplicate
}
}
Label {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: clientIdTextField.isDuplicate ? qsTr("%1 is already used").arg(clientIdTextField.displayText) : qsTr("Can't be blank")
font.pixelSize: app.smallFont
Layout.alignment: Qt.AlignRight
color: "red"
visible: !clientIdTextField.isValid
}
RowLayout {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
spacing: app.margins
Label {
text: qsTr("Client ID:")
Layout.fillWidth: true
}
TextField {
id: clientIdTextField
Layout.fillWidth: true
text: root.policy ? root.policy.clientId : ""
onEditingFinished: root.policy.clientId = text
placeholderText: qsTr("E.g. Sensor_1")
property bool isEmpty: displayText.length === 0
property bool isDuplicate: clientIdTextField.displayText != root.policy.clientId && engine.nymeaConfiguration.mqttPolicies.getPolicy(clientIdTextField.displayText) !== null
property bool isValid: !isEmpty && !isDuplicate
}
}
Label {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: clientIdTextField.isDuplicate ? qsTr("%1 is already used").arg(clientIdTextField.displayText) : qsTr("Can't be blank")
font.pixelSize: app.smallFont
Layout.alignment: Qt.AlignRight
color: "red"
visible: !clientIdTextField.isValid
}
RowLayout {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Label {
text: qsTr("Username:")
Layout.fillWidth: true
}
TextField {
id: usernameTextField
Layout.fillWidth: true
text: root.policy ? root.policy.username : ""
onEditingFinished: root.policy.username = text
placeholderText: qsTr("Optional")
}
}
RowLayout {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Label {
text: qsTr("Password:")
Layout.fillWidth: true
}
RowLayout { RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
Label {
text: qsTr("Username:")
Layout.fillWidth: true
}
TextField { TextField {
id: usernameTextField id: passwordTextField
Layout.fillWidth: true Layout.fillWidth: true
text: root.policy ? root.policy.username : "" text: root.policy ? root.policy.password : ""
onEditingFinished: root.policy.username = text onEditingFinished: root.policy.password = text
placeholderText: qsTr("Optional") placeholderText: qsTr("Optional")
echoMode: hiddenPassword ? TextInput.Password : TextInput.Normal
property bool hiddenPassword: true
} }
} ColorIcon {
Layout.preferredHeight: app.iconSize
RowLayout { Layout.preferredWidth: height
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins name: "../images/eye.svg"
Label { color: passwordTextField.hiddenPassword ? keyColor : app.accentColor
text: qsTr("Password:") MouseArea {
Layout.fillWidth: true anchors.fill: parent
} onClicked: passwordTextField.hiddenPassword = !passwordTextField.hiddenPassword
RowLayout {
TextField {
id: passwordTextField
Layout.fillWidth: true
text: root.policy ? root.policy.password : ""
onEditingFinished: root.policy.password = text
placeholderText: qsTr("Optional")
echoMode: hiddenPassword ? TextInput.Password : TextInput.Normal
property bool hiddenPassword: true
}
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: height
name: "../images/eye.svg"
color: passwordTextField.hiddenPassword ? keyColor : app.accentColor
MouseArea {
anchors.fill: parent
onClicked: passwordTextField.hiddenPassword = !passwordTextField.hiddenPassword
}
}
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Allowed publish topics")
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: root.policy.allowedPublishTopicFilters
ScrollBar.vertical: ScrollBar {}
clip: true
delegate: NymeaListItemDelegate {
width: parent.width
text: modelData
canDelete: true
progressive: false
onDeleteClicked: {
root.policy.allowedPublishTopicFilters.splice(index, 1)
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
property bool add: false
TextField {
id: pubField
Layout.fillWidth: parent.add
Layout.preferredWidth: parent.add ? undefined : 0
Behavior on width {
NumberAnimation {}
}
}
Button {
Layout.fillWidth: !parent.add
text: parent.add ? qsTr("OK") : qsTr("Add")
enabled: !parent.add || pubField.displayText.length > 0
onClicked: {
if (parent.add) {
root.policy.allowedPublishTopicFilters.push(pubField.displayText)
pubField.clear();
}
parent.add = !parent.add;
}
}
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Allowed subscribe filters")
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: root.policy.allowedSubscribeTopicFilters
ScrollBar.vertical: ScrollBar {}
clip: true
delegate: NymeaListItemDelegate {
width: parent.width
text: modelData
canDelete: true
progressive: false
onDeleteClicked: {
root.policy.allowedSubscribeTopicFilters.splice(index, 1)
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
property bool add: false
TextField {
id: subField
Layout.fillWidth: parent.add
Layout.preferredWidth: parent.add ? undefined : 0
}
Button {
Layout.fillWidth: !parent.add;
Behavior on width { NumberAnimation {} }
text: parent.add ? qsTr("OK") : qsTr("Add")
enabled: !parent.add || subField.displayText.length > 0
onClicked: {
if (parent.add) {
root.policy.allowedSubscribeTopicFilters.push(subField.displayText)
subField.clear();
}
parent.add = !parent.add;
} }
} }
} }
} }
Label {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Allowed publish topics")
color: app.accentColor
}
Repeater {
model: root.policy.allowedPublishTopicFilters
delegate: NymeaListItemDelegate {
Layout.fillWidth: true
text: modelData
canDelete: true
progressive: false
onDeleteClicked: {
root.policy.allowedPublishTopicFilters.splice(index, 1)
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
property bool add: false
TextField {
id: pubField
Layout.fillWidth: parent.add
Layout.preferredWidth: parent.add ? undefined : 0
Behavior on width {
NumberAnimation {}
}
}
Button {
Layout.fillWidth: !parent.add
text: parent.add ? qsTr("OK") : qsTr("Add")
enabled: !parent.add || pubField.displayText.length > 0
onClicked: {
if (parent.add) {
root.policy.allowedPublishTopicFilters.push(pubField.displayText)
pubField.clear();
}
parent.add = !parent.add;
}
}
}
Label {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Allowed subscribe filters")
color: app.accentColor
}
Repeater {
model: root.policy.allowedSubscribeTopicFilters
delegate: NymeaListItemDelegate {
Layout.fillWidth: true
text: modelData
canDelete: true
progressive: false
onDeleteClicked: {
root.policy.allowedSubscribeTopicFilters.splice(index, 1)
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
property bool add: false
TextField {
id: subField
Layout.fillWidth: parent.add
Layout.preferredWidth: parent.add ? undefined : 0
}
Button {
Layout.fillWidth: !parent.add;
Behavior on width { NumberAnimation {} }
text: parent.add ? qsTr("OK") : qsTr("Add")
enabled: !parent.add || subField.displayText.length > 0
onClicked: {
if (parent.add) {
root.policy.allowedSubscribeTopicFilters.push(subField.displayText)
subField.clear();
}
parent.add = !parent.add;
}
}
}
} }

View File

@ -35,14 +35,9 @@ import QtQuick.Layouts 1.1
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("Network settings")
text: qsTr("Network settings")
onBackPressed: {
pageStack.pop();
}
}
NetworkManager { NetworkManager {
id: networkManager id: networkManager
@ -80,212 +75,222 @@ Page {
} }
} }
ColumnLayout { SettingsPageSectionHeader {
anchors.fill: parent text: qsTr("General")
}
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Current connection state") text: qsTr("Current connection state")
prominentSubText: false prominentSubText: false
subText: { subText: {
switch (networkManager.state) {
case NetworkManager.NetworkManagerStateUnknown:
return qsTr("Unknown");
case NetworkManager.NetworkManagerStateAsleep:
return qsTr("Asleep");
case NetworkManager.NetworkManagerStateDisconnected:
return qsTr("Disconnected")
case NetworkManager.NetworkManagerStateDisconnecting:
return qsTr("Disconnecting")
case NetworkManager.NetworkManagerStateConnecting:
return qsTr("Connecting")
case NetworkManager.NetworkManagerStateConnectedLocal:
return qsTr("Locally connected")
case NetworkManager.NetworkManagerStateConnectedSite:
return qsTr("Site connected")
case NetworkManager.NetworkManagerStateConnectedGlobal:
return qsTr("Globally connected")
}
}
progressive: false
additionalItem: Led {
anchors.verticalCenter: parent.verticalCenter
state: {
switch (networkManager.state) { switch (networkManager.state) {
case NetworkManager.NetworkManagerStateUnknown: case NetworkManager.NetworkManagerStateUnknown:
return qsTr("Unknown");
case NetworkManager.NetworkManagerStateAsleep: case NetworkManager.NetworkManagerStateAsleep:
return qsTr("Asleep"); return "off";
case NetworkManager.NetworkManagerStateDisconnected: case NetworkManager.NetworkManagerStateDisconnected:
return qsTr("Disconnected")
case NetworkManager.NetworkManagerStateDisconnecting: case NetworkManager.NetworkManagerStateDisconnecting:
return qsTr("Disconnecting") return "red"
case NetworkManager.NetworkManagerStateConnecting: case NetworkManager.NetworkManagerStateConnecting:
return qsTr("Connecting")
case NetworkManager.NetworkManagerStateConnectedLocal: case NetworkManager.NetworkManagerStateConnectedLocal:
return qsTr("Locally connected")
case NetworkManager.NetworkManagerStateConnectedSite: case NetworkManager.NetworkManagerStateConnectedSite:
return qsTr("Site connected") return "orange"
case NetworkManager.NetworkManagerStateConnectedGlobal: case NetworkManager.NetworkManagerStateConnectedGlobal:
return qsTr("Globally connected") return "green";
} }
} }
progressive: false
additionalItem: Led {
state: {
switch (networkManager.state) {
case NetworkManager.NetworkManagerStateUnknown:
case NetworkManager.NetworkManagerStateAsleep:
return "off";
case NetworkManager.NetworkManagerStateDisconnected:
case NetworkManager.NetworkManagerStateDisconnecting:
return "red"
case NetworkManager.NetworkManagerStateConnecting:
case NetworkManager.NetworkManagerStateConnectedLocal:
case NetworkManager.NetworkManagerStateConnectedSite:
return "orange"
case NetworkManager.NetworkManagerStateConnectedGlobal:
return "green";
}
}
}
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Networking enabled")
subText: qsTr("Enable or disable networking altogether")
prominentSubText: false
progressive: false
additionalItem: Switch {
checked: networkManager.networkingEnabled
onClicked: {
if (!checked) {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Disabling networking will disconnect all connected clients. Be aware that you will not be able to interact remotely with this %1 system any more. Do not proceed unless you know what your are doing.").arg(app.systemName)
+ "\n\n"
+ qsTr("Do you want to proceed?")
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Disable networking?"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
networkManager.enableNetworking(false);
})
popup.rejected.connect(function() {
checked = true;
})
} else {
networkManager.enableNetworking(true);
}
}
}
}
ThinDivider {}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Wired network")
subText: qsTr("Shows the current ethernet status")
progressive: false
prominentSubText: false
}
Repeater {
model: networkManager.wiredNetworkDevices
NymeaListItemDelegate {
Layout.fillWidth: true
iconName: model.pluggedIn ? "../images/network-wired.svg" : "../images/network-wired-offline.svg"
text: model.interface + " (" + model.macAddress + ")"
subText: {
var ret = model.pluggedIn ? qsTr("Plugged in") : qsTr("Unplugged")
ret += " - "
ret += networkStateToString(model.state)
return ret;
}
progressive: false
}
}
ThinDivider {}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Wireless network")
subText: qsTr("Enable or disable WiFi")
progressive: false
prominentSubText: false
additionalItem: Switch {
checked: networkManager.wirelessNetworkingEnabled
onClicked: {
if (!checked) {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Disabling WiFi will disconnect all clients connected via WiFi. Be aware that you will not be able to interact remotely with this %1 system any more unless a LAN cable is connected.").arg(app.systemName)
+ "\n\n"
+ qsTr("Do you want to proceed?")
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Disable WiFi?"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
networkManager.enableWirelessNetworking(false);
})
popup.rejected.connect(function() {
checked = true;
})
} else {
networkManager.enableWirelessNetworking(true);
}
}
}
}
Repeater {
model: networkManager.wirelessNetworkDevices
NymeaListItemDelegate {
Layout.fillWidth: true
iconName: {
switch (model.state) {
case NetworkDevice.NetworkDeviceStateUnknown:
case NetworkDevice.NetworkDeviceStateUnmanaged:
case NetworkDevice.NetworkDeviceStateUnavailable:
case NetworkDevice.NetworkDeviceStateDisconnected:
case NetworkDevice.NetworkDeviceStateDeactivating:
case NetworkDevice.NetworkDeviceStateFailed:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStatePrepare:
return "../images/network-wifi.svg";
case NetworkDevice.NetworkDeviceStateConfig:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStateNeedAuth:
return "../images/network-wifi.svg";
case NetworkDevice.NetworkDeviceStateIpConfig:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStateIpCheck:
return "../images/network-wifi.svg";
case NetworkDevice.NetworkDeviceStateSecondaries:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStateActivated:
return "../images/network-wifi.svg";
}
console.warn("Unhandled enum", model.state)
}
text: model.interface + " (" + model.macAddress + ")"
subText: networkStateToString(model.state)
onClicked: {
var wirelessNetworkDevice = networkManager.wirelessNetworkDevices.getWirelessNetworkDevice(model.interface);
if (wirelessNetworkDevice.state === NetworkDevice.NetworkDeviceStateDisconnected) {
networkManager.refreshWifis(model.interface)
pageStack.push(wirelessAccessPointsPageComponent, {wirelessNetworkDevice: wirelessNetworkDevice})
} else {
pageStack.push(currentApPageComponent, {wirelessNetworkDevice: wirelessNetworkDevice})
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
} }
} }
Component {
id: wirelessAccessPointsPageComponent NymeaListItemDelegate {
Page { Layout.fillWidth: true
id: wirelessAccessPointsPage text: qsTr("Networking enabled")
header: NymeaHeader { subText: qsTr("Enable or disable networking altogether")
text: qsTr("WiFi networks") prominentSubText: false
onBackPressed: { progressive: false
pageStack.pop(); additionalItem: Switch {
anchors.verticalCenter: parent.verticalCenter
checked: networkManager.networkingEnabled
onClicked: {
if (!checked) {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Disabling networking will disconnect all connected clients. Be aware that you will not be able to interact remotely with this %1 system any more. Do not proceed unless you know what your are doing.").arg(app.systemName)
+ "\n\n"
+ qsTr("Do you want to proceed?")
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Disable networking?"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
networkManager.enableNetworking(false);
})
popup.rejected.connect(function() {
checked = true;
})
} else {
networkManager.enableNetworking(true);
} }
} }
}
}
SettingsPageSectionHeader {
text: qsTr("Wired network")
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("No wired network interfaces available")
wrapMode: Text.WordWrap
visible: networkManager.wiredNetworkDevices.count == 0
}
Repeater {
model: networkManager.wiredNetworkDevices
NymeaListItemDelegate {
Layout.fillWidth: true
iconName: model.pluggedIn ? "../images/network-wired.svg" : "../images/network-wired-offline.svg"
text: model.interface + " (" + model.macAddress + ")"
subText: {
var ret = model.pluggedIn ? qsTr("Plugged in") : qsTr("Unplugged")
ret += " - "
ret += networkStateToString(model.state)
return ret;
}
progressive: false
}
}
SettingsPageSectionHeader {
text: qsTr("Wireless network")
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Enabled")
subText: qsTr("Enable or disable WiFi")
progressive: false
prominentSubText: false
additionalItem: Switch {
anchors.verticalCenter: parent.verticalCenter
checked: networkManager.wirelessNetworkingEnabled
onClicked: {
if (!checked) {
var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml"));
var text = qsTr("Disabling WiFi will disconnect all clients connected via WiFi. Be aware that you will not be able to interact remotely with this %1 system any more unless a LAN cable is connected.").arg(app.systemName)
+ "\n\n"
+ qsTr("Do you want to proceed?")
var popup = dialog.createObject(app,
{
headerIcon: "../images/dialog-warning-symbolic.svg",
title: qsTr("Disable WiFi?"),
text: text,
standardButtons: Dialog.Ok | Dialog.Cancel
});
popup.open();
popup.accepted.connect(function() {
networkManager.enableWirelessNetworking(false);
})
popup.rejected.connect(function() {
checked = true;
})
} else {
networkManager.enableWirelessNetworking(true);
}
}
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("No wired network interfaces available")
wrapMode: Text.WordWrap
visible: networkManager.wirelessNetworkDevices.count == 0
}
Repeater {
model: networkManager.wirelessNetworkDevices
NymeaListItemDelegate {
Layout.fillWidth: true
iconName: {
switch (model.state) {
case NetworkDevice.NetworkDeviceStateUnknown:
case NetworkDevice.NetworkDeviceStateUnmanaged:
case NetworkDevice.NetworkDeviceStateUnavailable:
case NetworkDevice.NetworkDeviceStateDisconnected:
case NetworkDevice.NetworkDeviceStateDeactivating:
case NetworkDevice.NetworkDeviceStateFailed:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStatePrepare:
return "../images/network-wifi.svg";
case NetworkDevice.NetworkDeviceStateConfig:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStateNeedAuth:
return "../images/network-wifi.svg";
case NetworkDevice.NetworkDeviceStateIpConfig:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStateIpCheck:
return "../images/network-wifi.svg";
case NetworkDevice.NetworkDeviceStateSecondaries:
return "../images/network-wifi-offline.svg"
case NetworkDevice.NetworkDeviceStateActivated:
return "../images/network-wifi.svg";
}
console.warn("Unhandled enum", model.state)
}
text: model.interface + " (" + model.macAddress + ")"
subText: networkStateToString(model.state)
onClicked: {
var wirelessNetworkDevice = networkManager.wirelessNetworkDevices.getWirelessNetworkDevice(model.interface);
if (wirelessNetworkDevice.state === NetworkDevice.NetworkDeviceStateDisconnected) {
networkManager.refreshWifis(model.interface)
pageStack.push(wirelessAccessPointsPageComponent, {wirelessNetworkDevice: wirelessNetworkDevice})
} else {
pageStack.push(currentApPageComponent, {wirelessNetworkDevice: wirelessNetworkDevice})
}
}
}
}
Component {
id: wirelessAccessPointsPageComponent
SettingsPageBase {
id: wirelessAccessPointsPage
title: qsTr("WiFi networks")
property var wirelessNetworkDevice: null property var wirelessNetworkDevice: null
@ -294,13 +299,11 @@ Page {
accessPoints: wirelessAccessPointsPage.wirelessNetworkDevice.accessPoints accessPoints: wirelessAccessPointsPage.wirelessNetworkDevice.accessPoints
} }
ListView { Repeater {
id: listView id: listView
anchors.fill: parent
model: apProxy model: apProxy
ScrollBar.vertical: ScrollBar {}
delegate: NymeaListItemDelegate { delegate: NymeaListItemDelegate {
width: parent.width Layout.fillWidth: true
text: model.ssid text: model.ssid
subText: model.macAddress subText: model.macAddress
iconName: { iconName: {
@ -334,106 +337,95 @@ Page {
} }
} }
Component { Component {
id: authPageComponent id: authPageComponent
Page { SettingsPageBase {
id: authPage id: authPage
title: qsTr("Authenticate")
header: NymeaHeader {
text: qsTr("Authenticate")
onBackPressed: pageStack.pop()
}
property var wirelessNetworkDevice: null property var wirelessNetworkDevice: null
property var wirelessAccessPoint: null property var wirelessAccessPoint: null
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
Label { Label {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Enter the password for %1").arg(authPage.wirelessAccessPoint.ssid)
wrapMode: Text.WordWrap
}
RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
TextField {
id: passwordTextField
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: app.margins property bool showPassword: false
text: qsTr("Enter the password for %1").arg(authPage.wirelessAccessPoint.ssid) echoMode: showPassword ? TextInput.Normal : TextInput.Password
wrapMode: Text.WordWrap
} }
RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
TextField {
id: passwordTextField
Layout.fillWidth: true
property bool showPassword: false
echoMode: showPassword ? TextInput.Normal : TextInput.Password
}
ColorIcon { ColorIcon {
Layout.preferredHeight: app.iconSize Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize Layout.preferredWidth: app.iconSize
name: "../images/eye.svg" name: "../images/eye.svg"
color: passwordTextField.showPassword ? app.accentColor : keyColor color: passwordTextField.showPassword ? app.accentColor : keyColor
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: passwordTextField.showPassword = !passwordTextField.showPassword onClicked: passwordTextField.showPassword = !passwordTextField.showPassword
}
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("OK")
enabled: passwordTextField.displayText.length >= 8
onClicked: {
networkManager.connectToWiFi(authPage.wirelessNetworkDevice.interface, authPage.wirelessAccessPoint.ssid, passwordTextField.text)
pageStack.pop(root);
} }
} }
} }
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("OK")
enabled: passwordTextField.displayText.length >= 8
onClicked: {
networkManager.connectToWiFi(authPage.wirelessNetworkDevice.interface, authPage.wirelessAccessPoint.ssid, passwordTextField.text)
pageStack.pop(root);
}
}
} }
} }
Component { Component {
id: currentApPageComponent id: currentApPageComponent
Page { SettingsPageBase {
id: currentApPage id: currentApPage
header: NymeaHeader { title: qsTr("Current connection")
text: qsTr("Current connection")
onBackPressed: pageStack.pop();
}
property WirelessNetworkDevice wirelessNetworkDevice: null property WirelessNetworkDevice wirelessNetworkDevice: null
GridLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
columns: 1
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("SSID") text: qsTr("SSID")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.ssid subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.ssid
progressive: false progressive: false
} }
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("MAC Address") text: qsTr("MAC Address")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.macAddress subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.macAddress
progressive: false progressive: false
} }
NymeaListItemDelegate { NymeaListItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Signal strength") text: qsTr("Signal strength")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.signalStrength subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.signalStrength
progressive: false progressive: false
} }
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: app.margins Layout.margins: app.margins
text: qsTr("Disconnect") text: qsTr("Disconnect")
onClicked: { onClicked: {
networkManager.disconnectInterface(currentApPage.wirelessNetworkDevice.interface) networkManager.disconnectInterface(currentApPage.wirelessNetworkDevice.interface)
pageStack.pop(root); pageStack.pop(root);
}
} }
} }
} }
} }
} }

View File

@ -35,7 +35,7 @@ import QtQuick.Layouts 1.3
import "../components" import "../components"
import Nymea 1.0 import Nymea 1.0
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { header: NymeaHeader {
text: qsTr("Plugins") text: qsTr("Plugins")
@ -51,36 +51,26 @@ Page {
} }
} }
ColumnLayout { Label {
anchors.fill: parent Layout.fillWidth: true
Layout.margins: app.margins
Label { wrapMode: Text.WordWrap
Layout.fillWidth: true text: qsTr("This list shows the list of installed plugins on this %1 system.").arg(app.systemName)
Layout.margins: app.margins
wrapMode: Text.WordWrap
text: qsTr("This list shows the list of installed plugins on this %1 system.").arg(app.systemName)
}
ThinDivider {}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: PluginsProxy {
id: pluginsProxy
plugins: engine.deviceManager.plugins
}
clip: true
delegate: NymeaListItemDelegate {
property var plugin: pluginsProxy.get(index)
width: parent.width
iconName: "../images/plugin.svg"
text: model.name
progressive: plugin.paramTypes.count > 0
onClicked: pageStack.push(Qt.resolvedUrl("PluginParamsPage.qml"), {plugin: plugin})
}
}
} }
Repeater {
model: PluginsProxy {
id: pluginsProxy
plugins: engine.deviceManager.plugins
}
delegate: NymeaListItemDelegate {
Layout.fillWidth: true
property var plugin: pluginsProxy.get(index)
iconName: "../images/plugin.svg"
text: model.name
progressive: plugin.paramTypes.count > 0
onClicked: pageStack.push(Qt.resolvedUrl("PluginParamsPage.qml"), {plugin: plugin})
}
}
} }

View File

@ -0,0 +1,133 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
SettingsPageBase {
id: root
title: qsTr("User settings")
UserManager {
id: userManager
engine: _engine
onChangePasswordResponse: {
if (error != UserManager.UserErrorNoError) {
var component = Qt.createComponent("../components/ErrorDialog.qml")
var text;
switch (error) {
case UserManager.UserErrorBadPassword:
text = qsTr("The given password is not valid.");
break;
case UserManager.UserErrorPermissionDenied:
text = qsTr("Permission denied.");
break;
case UserManager.UserErrorBackendError:
text = qsTr("The new password could not be stored.")
break;
default:
text = qsTr("Un unexpected error happened when changing the password. We're sorry for this. (Error code: %1)").arg(error);
break;
}
var popup = component.createObject(app, {text: text});
popup.open()
}
}
}
SettingsPageSectionHeader {
text: qsTr("User info")
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: userManager.userInfo.username
subText: qsTr("Username")
progressive: false
prominentSubText: false
iconName: "../images/account.svg"
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Change password")
iconName: "../images/key.svg"
onClicked: {
var page = pageStack.push(changePasswordComponent)
page.confirmed.connect(function(newPassword) {
userManager.changePassword(newPassword)
})
}
}
SettingsPageSectionHeader {
text: qsTr("Devices / Apps accessing nymea:core")
}
Repeater {
model: userManager.tokenInfos
delegate: NymeaListItemDelegate {
Layout.fillWidth: true
text: model.deviceName
subText: qsTr("Created on %1").arg(Qt.formatDateTime(model.creationTime, Qt.DefaultLocaleShortDate))
prominentSubText: false
progressive: false
canDelete: true
onDeleteClicked: {
userManager.removeToken(model.id)
}
}
}
Component {
id: changePasswordComponent
SettingsPageBase {
id: changePasswordPage
title: qsTr("Change password")
signal confirmed(string newPassword)
SettingsPageSectionHeader {
text: qsTr("Change password")
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Please enter the new password for %1").arg(userManager.userInfo.username)
wrapMode: Text.WordWrap
}
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
minPasswordLength: 8
requireLowerCaseLetter: true
requireUpperCaseLetter: true
requireNumber: true
requireSpecialChar: false
signup: true
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
Layout.margins: app.margins
enabled: passwordTextField.isValid
onClicked: {
changePasswordPage.confirmed(passwordTextField.password)
pageStack.pop();
}
}
}
}
}

View File

@ -34,73 +34,53 @@ import QtQuick.Layouts 1.3
import Nymea 1.0 import Nymea 1.0
import "../components" import "../components"
Page { SettingsPageBase {
id: root id: root
header: NymeaHeader { title: qsTr("Web server")
text: qsTr("Web server")
onBackPressed: pageStack.pop(); SettingsPageSectionHeader {
text: qsTr("Web server interfaces")
} }
Flickable { Repeater {
anchors.fill: parent model: engine.nymeaConfiguration.webServerConfigurations
contentHeight: connectionsColumn.implicitHeight delegate: ConnectionInterfaceDelegate {
interactive: contentHeight > height Layout.fillWidth: true
canDelete: true
ColumnLayout { onClicked: {
id: connectionsColumn var component = Qt.createComponent(Qt.resolvedUrl("WebServerConfigurationDialog.qml"));
anchors { left: parent.left; top: parent.top; right: parent.right } var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.webServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
Label { engine.nymeaConfiguration.setWebServerConfiguration(popup.serverConfiguration)
Layout.fillWidth: true popup.serverConfiguration.destroy();
Layout.leftMargin: app.margins })
Layout.rightMargin: app.margins popup.rejected.connect(function() {
Layout.topMargin: app.margins popup.serverConfiguration.destroy();
text: qsTr("Web server interfaces") })
wrapMode: Text.WordWrap popup.open()
color: app.accentColor
} }
onDeleteClicked: {
Repeater { print("should delete")
model: engine.nymeaConfiguration.webServerConfigurations engine.nymeaConfiguration.deleteWebServerConfiguration(model.id)
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
canDelete: true
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("WebServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.webServerConfigurations.get(index).clone() });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setWebServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
onDeleteClicked: {
print("should delete")
engine.nymeaConfiguration.deleteWebServerConfiguration(model.id)
}
}
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createWebServerConfiguration("0.0.0.0", 80 + engine.nymeaConfiguration.webServerConfigurations.count, false, false, "/var/www/");
var component = Qt.createComponent(Qt.resolvedUrl("WebServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setWebServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
} }
} }
} }
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Add")
onClicked: {
var config = engine.nymeaConfiguration.createWebServerConfiguration("0.0.0.0", 80 + engine.nymeaConfiguration.webServerConfigurations.count, false, false, "/var/www/");
var component = Qt.createComponent(Qt.resolvedUrl("WebServerConfigurationDialog.qml"));
var popup = component.createObject(root, { serverConfiguration: config });
popup.accepted.connect(function() {
engine.nymeaConfiguration.setWebServerConfiguration(popup.serverConfiguration)
popup.serverConfiguration.destroy();
})
popup.rejected.connect(function() {
popup.serverConfiguration.destroy();
})
popup.open()
}
}
} }