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.setValue("url", connection->url());
settings.setValue("bearerType", connection->bearerType());
settings.value("secure", connection->secure());
settings.setValue("secure", connection->secure());
settings.setValue("displayName", connection->displayName());
settings.endGroup();
}

View File

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

View File

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

View File

@ -50,6 +50,7 @@ class JsonRpcClient : public JsonHandler
Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged)
Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged)
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(QString serverVersion READ serverVersion NOTIFY handshakeReceived)
Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived)
@ -81,6 +82,7 @@ public:
bool initialSetupRequired() const;
bool authenticationRequired() const;
bool pushButtonAuthAvailable() const;
bool authenticated() const;
CloudConnectionState cloudConnectionState() const;
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 authenticationRequiredChanged();
void pushButtonAuthAvailableChanged();
void authenticatedChanged();
void connectedChanged(bool connected);
void tokenChanged();
void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion);
@ -135,6 +138,7 @@ private:
bool m_initialSetupRequired = false;
bool m_authenticationRequired = false;
bool m_pushButtonAuthAvailable = false;
bool m_authenticated = false;
CloudConnectionState m_cloudConnectionState = CloudConnectionStateDisabled;
int m_pendingPushButtonTransaction = -1;
QString m_serverUuid;

View File

@ -105,6 +105,10 @@
#include "types/script.h"
#include "types/scripts.h"
#include "types/types.h"
#include "usermanager.h"
#include "types/tokeninfos.h"
#include "types/tokeninfo.h"
#include "types/userinfo.h"
#include <QtQml/qqml.h>
@ -281,6 +285,11 @@ void registerQmlTypes() {
qmlRegisterType<ScriptSyntaxHighlighter>(uri, 1, 0, "ScriptSyntaxHighlighter");
qmlRegisterType<CodeCompletion>(uri, 1, 0, "CodeCompletion");
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

View File

@ -55,6 +55,7 @@ SOURCES += \
scripting/completionmodel.cpp \
scriptmanager.cpp \
scriptsyntaxhighlighter.cpp \
usermanager.cpp \
vendorsproxy.cpp \
pluginsproxy.cpp \
interfacesmodel.cpp \
@ -91,6 +92,7 @@ SOURCES += \
models/devicemodel.cpp \
system/systemcontroller.cpp \
HEADERS += \
configuration/networkmanager.h \
engine.h \
@ -122,6 +124,7 @@ HEADERS += \
scripting/completionmodel.h \
scriptmanager.h \
scriptsyntaxhighlighter.h \
usermanager.h \
vendorsproxy.h \
pluginsproxy.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/wirelessaccesspoint.h \
types/wirelessaccesspoints.h \
types/tokeninfo.h \
types/tokeninfos.h \
types/userinfo.h \
SOURCES += \
types/browseritem.cpp \
@ -122,3 +126,8 @@ SOURCES += \
types/tags.cpp \
types/wirelessaccesspoint.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/edit-clear.svg</file>
<file>ui/images/smartlock.svg</file>
<file>ui/images/key.svg</file>
</qresource>
</RCC>

View File

@ -208,5 +208,8 @@
<file>ui/magic/scripting/LineNumbers.qml</file>
<file>ui/magic/scripting/CompletionBox.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>
</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 {
Layout.fillWidth: true
Material.elevation: layout.isGrid ? 1 : 0

View File

@ -35,46 +35,37 @@ import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("About %1").arg(app.appName)
backButtonVisible: true
onBackPressed: pageStack.pop()
}
title: qsTr("About %1").arg(app.appName)
Flickable {
anchors.fill: parent
contentHeight: imprint.implicitHeight
Imprint {
id: imprint
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 {
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"
githubLink: "https://github.com/nymea/nymea-app"
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("App version:")
subText: appVersion
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Qt version:")
subText: qtVersion + (qtBuildVersion !== qtVersion ? " (" + qsTr("Built with %1").arg(qtBuildVersion) + ")" : "")
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("App version:")
subText: appVersion
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Qt version:")
subText: qtVersion + (qtBuildVersion !== qtVersion ? " (" + qsTr("Built with %1").arg(qtBuildVersion) + ")" : "")
progressive: false
prominentSubText: false
}
}
}

View File

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

View File

@ -34,12 +34,9 @@ import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Cloud login")
onBackPressed: pageStack.pop()
}
title: qsTr("%1 cloud login").arg(app.appName)
Component.onCompleted: {
if (AWSClient.isLoggedIn) {
@ -50,13 +47,13 @@ Page {
Connections {
target: AWSClient
onLoginResult: {
busyOverlay.shown = false;
root.busy = false;
if (error === AWSClient.LoginErrorNoError) {
AWSClient.fetchDevices();
}
}
onDeleteAccountResult: {
busyOverlay.shown = false;
root.busy = false;
if (error !== AWSClient.LoginErrorNoError) {
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
var text = qsTr("Sorry, an error happened removing the account. Please try again later.");
@ -68,12 +65,15 @@ Page {
}
ColumnLayout {
anchors.fill: parent
Layout.fillWidth: true
visible: AWSClient.isLoggedIn
SettingsPageSectionHeader {
text: qsTr("Login")
}
Label {
Layout.fillWidth: true
Layout.topMargin: app.margins
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
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 {
Layout.fillWidth: true
Layout.topMargin: app.margins
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
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 %n %1:core systems connected to your cloud.", "", AWSClient.awsDevices.count).arg(app.systemName)
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Repeater {
model: AWSClient.awsDevices
delegate: NymeaListItemDelegate {
width: parent.width
Layout.fillWidth: true
text: model.name
subText: model.id
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 {
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
id: signupPageComponent
SettingsPageBase {
id: signupPage
title: qsTr("Sign up")
RowLayout {
CheckBox {
id: deleteCheckbox
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Delete my account")
Connections {
target: AWSClient
onSignupResult: {
signupPage.busy = 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()
}
}
onAccepted: {
if (deleteCheckbox.checked) {
busyOverlay.shown = true;
AWSClient.deleteAccount()
} else {
AWSClient.logout()
}
SettingsPageSectionHeader {
text: qsTr("Welcome to %1:cloud.").arg(app.systemName)
}
}
}
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 {
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
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 {
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)
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)
}
@ -212,198 +326,32 @@ Page {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("Password")
}
RowLayout {
PasswordTextField {
id: passwordTextField
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
signup: false
}
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("OK")
enabled: usernameTextField.acceptableInput
onClicked: {
busyOverlay.shown = 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?")
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")
onClicked: {
pageStack.push(signupPageComponent)
}
}
}
}
BusyOverlay {
id: busyOverlay
}
Component {
id: signupPageComponent
Page {
id: signupPage
header: NymeaHeader {
Layout.margins: app.margins
text: qsTr("Sign up")
onBackPressed: pageStack.pop()
}
Flickable {
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)
}
}
enabled: usernameTextField.acceptableInput && passwordTextField.isValid
onClicked: {
signupPage.busy = 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 {
@ -436,7 +384,7 @@ Page {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
text: qsTr("OK")
onClicked: {
busyOverlay.shown = true;
root.busy = true;
AWSClient.confirmRegistration(confirmationCodeTextField.text)
}
}
@ -444,7 +392,7 @@ Page {
Connections {
target: AWSClient
onConfirmationResult: {
busyOverlay.shown = false;
root.busy = false;
var text
switch (error) {
case AWSClient.LoginErrorNoError:
@ -473,20 +421,16 @@ Page {
Component {
id: resetPasswordComponent
Page {
SettingsPageBase {
id: resetPasswordPage
title: qsTr("Reset password")
property alias email: emailTextField.text
header: NymeaHeader {
text: qsTr("Reset password")
onBackPressed: pageStack.pop()
}
Connections {
target: AWSClient
onForgotPasswordResult: {
busyOverlay.shown = false
resetPasswordPage.busy = false
if (error !== AWSClient.LoginErrorNoError) {
var errorDialog = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"));
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 {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Password forgotten?")
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 {
Layout.fillWidth: true
Layout.margins: app.margins
wrapMode: Text.WordWrap
text: qsTr("Password forgotten?")
font.pixelSize: app.largeFont
color: app.accentColor
}
BusyOverlay {
id: busyOverlay
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)
resetPasswordPage.busy = true
}
}
}
}
@ -540,13 +477,13 @@ Page {
Component {
id: confirmResetPasswordComponent
Page {
SettingsPageBase {
id: confirmResetPasswordPage
Connections {
target: AWSClient
onConfirmForgotPasswordResult: {
busyOverlay.shown = false
confirmResetPasswordPage.busy = false
if (error !== AWSClient.LoginErrorNoError) {
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?")})
@ -564,63 +501,85 @@ Page {
}
property string email
header: NymeaHeader {
text: qsTr("Reset password")
onBackPressed: pageStack.pop()
title: qsTr("Reset password")
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 {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("Check your email!")
color: app.accentColor
font.pixelSize: app.largeFont
text: qsTr("Delete my account")
}
}
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: {
busyOverlay.shown = true
AWSClient.confirmForgotPassword(confirmResetPasswordPage.email, codeTextField.text, passwordTextField.password)
}
}
BusyOverlay {
id: busyOverlay
onAccepted: {
if (deleteCheckbox.checked) {
root.busy = true;
AWSClient.deleteAccount()
} else {
AWSClient.logout()
}
}
}

View File

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

View File

@ -35,167 +35,195 @@ import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Look and feel")
backButtonVisible: true
onBackPressed: pageStack.pop()
title: qsTr("Look and feel")
SettingsPageSectionHeader {
text: qsTr("Appearance")
}
ColumnLayout {
id: contentColumn
width: parent.width
RowLayout {
Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
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 {
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: appBranding.length === 0
Label {
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";
}
text: "Style"
}
ComboBox {
model: styleController.allStyles
currentIndex: styleController.allStyles.indexOf(styleController.currentStyle)
onActivated: {
styleController.currentStyle = model[index]
}
}
CheckDelegate {
Layout.fillWidth: true
text: qsTr("Return to home on idle")
checked: settings.returnToHome
onClicked: settings.returnToHome = checked
}
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")
}
Connections {
target: styleController
onCurrentStyleChanged: {
var popup = styleChangedDialog.createObject(root)
popup.open()
}
}
}
ItemDelegate {
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: !kioskMode && Qt.platform.os !== "ios"
Label {
Layout.fillWidth: true
visible: PlatformHelper.canControlScreen
topPadding: 0
contentItem: RowLayout {
Label {
Layout.fillWidth: true
text: qsTr("Screen brightness")
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;
}
Slider {
Layout.fillWidth: true
value: PlatformHelper.screenBrightness
onMoved: PlatformHelper.screenBrightness = value
from: 0
to: 100
stepSize: 1
}
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;
}
}
}
}
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 {
id: styleChangedDialog
Dialog {

View File

@ -99,7 +99,7 @@ ColumnLayout {
}
var ret = []
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 += "</font>"
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();
}
onCreateUserSucceeded: {
engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app");
engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app (" + PlatformHelper.deviceModel + ")");
}
onCreateUserFailed: {
@ -153,7 +153,7 @@ Page {
engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.password);
} else {
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("About %1:core").arg(app.systemName)
onBackPressed: pageStack.pop()
}
title: qsTr("About %1:core").arg(app.systemName)
Flickable {
anchors.fill: parent
contentHeight: imprint.implicitHeight
Imprint {
id: imprint
width: parent.width
title: qsTr("%1:core").arg(app.systemName)
githubLink: "https://github.com/nymea/nymea"
Imprint {
id: imprint
Layout.fillWidth: true
title: qsTr("%1:core").arg(app.systemName)
githubLink: "https://github.com/nymea/nymea"
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Connection:")
subText: engine.connection.currentConnection.url
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Server UUID:")
subText: engine.jsonRpcClient.serverUuid
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Server version:")
subText: engine.jsonRpcClient.serverVersion
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("JSON-RPC version:")
subText: engine.jsonRpcClient.jsonRpcVersion
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Qt version:")
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
subText: engine.jsonRpcClient.serverQtVersion + (engine.jsonRpcClient.serverQtVersion !== engine.jsonRpcClient.serverQtBuildVersion ? + " (" + qsTr("Built with %1").arg(engine.jsonRpcClient.serverQtBuildVersion) + ")" : "")
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Connection:")
subText: engine.connection.currentConnection.url
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Server UUID:")
subText: engine.jsonRpcClient.serverUuid
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Server version:")
subText: engine.jsonRpcClient.serverVersion
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("JSON-RPC version:")
subText: engine.jsonRpcClient.jsonRpcVersion
progressive: false
prominentSubText: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Qt version:")
visible: engine.jsonRpcClient.ensureServerVersion("4.1")
subText: engine.jsonRpcClient.serverQtVersion + (engine.jsonRpcClient.serverQtVersion !== engine.jsonRpcClient.serverQtBuildVersion ? + " (" + qsTr("Built with %1").arg(engine.jsonRpcClient.serverQtBuildVersion) + ")" : "")
progressive: false
prominentSubText: false
}
}
}

View File

@ -34,12 +34,9 @@ import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Cloud settings")
onBackPressed: pageStack.pop();
}
title: qsTr("%1:core cloud settings").arg(app.systemName)
Item {
id: d
@ -59,97 +56,120 @@ Page {
}
}
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
SettingsPageSectionHeader {
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 {
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
}
// 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;
}
}
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);
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 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);
}
}
BusyIndicator {
id: busyIndicator
visible: (engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateUnconfigured && d.deploymentStarted) ||
engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateConnecting
return engine.jsonRpcClient.cloudConnectionState
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
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
BusyIndicator {
id: busyIndicator
visible: (engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateUnconfigured && d.deploymentStarted) ||
engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateConnecting
}
}
Button {
Layout.fillWidth: true
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"))
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
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 {
Layout.fillWidth: true
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Connection interfaces")
onBackPressed: pageStack.pop();
title: qsTr("Connection interfaces")
SettingsPageSectionHeader {
text: qsTr("TCP server interfaces")
}
Flickable {
anchors.fill: parent
contentHeight: connectionsColumn.implicitHeight
interactive: contentHeight > height
ColumnLayout {
id: connectionsColumn
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
text: qsTr("TCP server interfaces")
wrapMode: Text.WordWrap
color: app.accentColor
Repeater {
model: engine.nymeaConfiguration.tcpServerConfigurations
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()
}
Repeater {
model: engine.nymeaConfiguration.tcpServerConfigurations
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()
}
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()
}
}
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Developer tools")
onBackPressed: pageStack.pop();
}
title: qsTr("Developer tools")
property WebServerConfiguration usedConfig: {
var config = null
@ -49,7 +46,7 @@ Page {
if (tmp.address === engine.connection.currentConnection.hostAddress || tmp.address === "0.0.0.0") {
// 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...
if (config === null || (config.sslEnabled && !tmp.sslEnabled)) {
@ -61,72 +58,66 @@ Page {
return config;
}
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
RowLayout {
Layout.fillWidth: true
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
}
}
SettingsPageSectionHeader {
text: qsTr("Debug server")
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("In order to access the debug interface, please enable the web server.")
font.pixelSize: app.smallFont
color: "red"
wrapMode: Text.WordWrap
visible: engine.nymeaConfiguration.webServerConfigurations.count === 0
}
SwitchDelegate {
id: debugServerEnabledSwitch
Layout.fillWidth: true
text: qsTr("Debug server enabled")
checked: engine.nymeaConfiguration.debugServerEnabled
onToggled: engine.nymeaConfiguration.debugServerEnabled = checked
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("The web server cannot be reached on %1.").arg(engine.connection.currentConnection.hostAddress)
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
color: "red"
visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("In order to access the debug interface, please enable the web server.")
font.pixelSize: app.smallFont
color: "red"
wrapMode: Text.WordWrap
visible: engine.nymeaConfiguration.webServerConfigurations.count === 0
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Please enable the web server to be accessed on this address.")
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null
}
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("The web server cannot be reached on %1.").arg(engine.connection.currentConnection.hostAddress)
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
color: "red"
visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null
}
Button {
id: debugServerButton
Layout.fillWidth: true
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)
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Please enable the web server to be accessed on this address.")
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null
}
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)
}
Button {
id: debugServerButton
Layout.fillWidth: true
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("General settings")
backButtonVisible: true
onBackPressed: pageStack.pop()
title: qsTr("General settings")
SettingsPageSectionHeader {
text: qsTr("General")
}
ColumnLayout {
id: settingsGrid
anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; margins: app.margins }
width: Math.min(500, parent.width - app.margins * 2)
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
spacing: app.margins
RowLayout {
Label {
text: qsTr("Name")
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
}
}
RowLayout {
TextField {
id: nameTextField
Layout.fillWidth: true
visible: engine.jsonRpcClient.ensureServerVersion("4.1") && engine.systemController.automaticTimeAvailable
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()
})
}
text: engine.nymeaConfiguration.serverName
}
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
text: qsTr("Shutdown %1:core").arg(app.systemName)
visible: engine.systemController.powerManagementAvailable
}
CheckBox {
checked: engine.systemController.automaticTime
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()
})
engine.systemController.automaticTime = checked
}
}
}
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 {
id: timePickerComponent
Dialog {

View File

@ -34,140 +34,102 @@ import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("MQTT broker")
onBackPressed: pageStack.pop();
title: qsTr("MQTT broker")
SettingsPageSectionHeader {
text: qsTr("MQTT Server Interfaces")
}
// Flickable {
// anchors.fill: parent
// contentHeight: connectionsColumn.implicitHeight
// interactive: contentHeight > height
Repeater {
model: engine.nymeaConfiguration.mqttServerConfigurations
ColumnLayout {
id: connectionsColumn
// anchors { left: parent.left; top: parent.top; right: parent.right }
anchors.fill: parent
// layoutDirection: Qt.
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.topMargin: app.margins
text: qsTr("MQTT Server Interfaces")
wrapMode: Text.WordWrap
color: app.accentColor
delegate: ConnectionInterfaceDelegate {
Layout.fillWidth: true
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()
}
ListView {
Layout.fillWidth: true
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
onDeleteClicked: {
engine.nymeaConfiguration.deleteMqttServerConfiguration(model.id)
}
}
// }
}
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Mqtt permission")
@ -57,175 +57,176 @@ Page {
signal accepted();
signal rejected()
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom }
RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: 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
}
Label {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Client info")
color: app.accentColor
}
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 {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
Label {
text: qsTr("Username:")
Layout.fillWidth: true
}
TextField {
id: usernameTextField
id: passwordTextField
Layout.fillWidth: true
text: root.policy ? root.policy.username : ""
onEditingFinished: root.policy.username = text
text: root.policy ? root.policy.password : ""
onEditingFinished: root.policy.password = text
placeholderText: qsTr("Optional")
echoMode: hiddenPassword ? TextInput.Password : TextInput.Normal
property bool hiddenPassword: true
}
}
RowLayout {
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
Label {
text: qsTr("Password:")
Layout.fillWidth: true
}
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;
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
}
}
}
}
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Network settings")
onBackPressed: {
pageStack.pop();
}
}
title: qsTr("Network settings")
NetworkManager {
id: networkManager
@ -80,212 +75,222 @@ Page {
}
}
ColumnLayout {
anchors.fill: parent
SettingsPageSectionHeader {
text: qsTr("General")
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Current connection state")
prominentSubText: false
subText: {
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Current connection state")
prominentSubText: false
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) {
case NetworkManager.NetworkManagerStateUnknown:
return qsTr("Unknown");
case NetworkManager.NetworkManagerStateAsleep:
return qsTr("Asleep");
return "off";
case NetworkManager.NetworkManagerStateDisconnected:
return qsTr("Disconnected")
case NetworkManager.NetworkManagerStateDisconnecting:
return qsTr("Disconnecting")
return "red"
case NetworkManager.NetworkManagerStateConnecting:
return qsTr("Connecting")
case NetworkManager.NetworkManagerStateConnectedLocal:
return qsTr("Locally connected")
case NetworkManager.NetworkManagerStateConnectedSite:
return qsTr("Site connected")
return "orange"
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
Page {
id: wirelessAccessPointsPage
header: NymeaHeader {
text: qsTr("WiFi networks")
onBackPressed: {
pageStack.pop();
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Networking enabled")
subText: qsTr("Enable or disable networking altogether")
prominentSubText: false
progressive: false
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
@ -294,13 +299,11 @@ Page {
accessPoints: wirelessAccessPointsPage.wirelessNetworkDevice.accessPoints
}
ListView {
Repeater {
id: listView
anchors.fill: parent
model: apProxy
ScrollBar.vertical: ScrollBar {}
delegate: NymeaListItemDelegate {
width: parent.width
Layout.fillWidth: true
text: model.ssid
subText: model.macAddress
iconName: {
@ -334,106 +337,95 @@ Page {
}
}
Component {
id: authPageComponent
Page {
SettingsPageBase {
id: authPage
header: NymeaHeader {
text: qsTr("Authenticate")
onBackPressed: pageStack.pop()
}
title: qsTr("Authenticate")
property var wirelessNetworkDevice: 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.margins: app.margins
text: qsTr("Enter the password for %1").arg(authPage.wirelessAccessPoint.ssid)
wrapMode: Text.WordWrap
property bool showPassword: false
echoMode: showPassword ? TextInput.Normal : TextInput.Password
}
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 {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: "../images/eye.svg"
color: passwordTextField.showPassword ? app.accentColor : keyColor
MouseArea {
anchors.fill: parent
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);
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: "../images/eye.svg"
color: passwordTextField.showPassword ? app.accentColor : keyColor
MouseArea {
anchors.fill: parent
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);
}
}
}
}
Component {
id: currentApPageComponent
Page {
SettingsPageBase {
id: currentApPage
header: NymeaHeader {
text: qsTr("Current connection")
onBackPressed: pageStack.pop();
}
title: qsTr("Current connection")
property WirelessNetworkDevice wirelessNetworkDevice: null
GridLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
columns: 1
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("SSID")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.ssid
progressive: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("MAC Address")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.macAddress
progressive: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Signal strength")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.signalStrength
progressive: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("SSID")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.ssid
progressive: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("MAC Address")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.macAddress
progressive: false
}
NymeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Signal strength")
subText: currentApPage.wirelessNetworkDevice.currentAccessPoint.signalStrength
progressive: false
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Disconnect")
onClicked: {
networkManager.disconnectInterface(currentApPage.wirelessNetworkDevice.interface)
pageStack.pop(root);
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Disconnect")
onClicked: {
networkManager.disconnectInterface(currentApPage.wirelessNetworkDevice.interface)
pageStack.pop(root);
}
}
}
}
}

View File

@ -35,7 +35,7 @@ import QtQuick.Layouts 1.3
import "../components"
import Nymea 1.0
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Plugins")
@ -51,36 +51,26 @@ Page {
}
}
ColumnLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
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})
}
}
Label {
Layout.fillWidth: true
Layout.margins: app.margins
wrapMode: Text.WordWrap
text: qsTr("This list shows the list of installed plugins on this %1 system.").arg(app.systemName)
}
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 "../components"
Page {
SettingsPageBase {
id: root
header: NymeaHeader {
text: qsTr("Web server")
onBackPressed: pageStack.pop();
title: qsTr("Web server")
SettingsPageSectionHeader {
text: qsTr("Web server interfaces")
}
Flickable {
anchors.fill: parent
contentHeight: connectionsColumn.implicitHeight
interactive: contentHeight > height
ColumnLayout {
id: connectionsColumn
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
text: qsTr("Web server interfaces")
wrapMode: Text.WordWrap
color: app.accentColor
Repeater {
model: engine.nymeaConfiguration.webServerConfigurations
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()
}
Repeater {
model: engine.nymeaConfiguration.webServerConfigurations
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()
}
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()
}
}
}