Finishing touches

This commit is contained in:
Michael Zanetti 2022-01-20 10:24:07 +01:00
parent 92327bb6ec
commit 3be00d1887
20 changed files with 706 additions and 142 deletions

View File

@ -379,11 +379,15 @@ QVariantMap JsonRpcClient::experiences() const
return m_experiences;
}
int JsonRpcClient::createUser(const QString &username, const QString &password)
int JsonRpcClient::createUser(const QString &username, const QString &password, const QString &displayName, const QString &email)
{
QVariantMap params;
params.insert("username", username);
params.insert("password", password);
if (ensureServerVersion("6.0")) {
params.insert("displayName", displayName);
params.insert("email", email);
}
JsonRpcReply* reply = createReply("JSONRPC.CreateUser", params, this, "processCreateUser");
m_replies.insert(reply->commandId(), reply);
m_connection->sendData(QJsonDocument::fromVariant(reply->requestMap()).toJson());
@ -639,7 +643,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
JsonRpcReply *reply = m_replies.take(commandId);
if (reply) {
reply->deleteLater();
// qDebug() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback();
qWarning() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback();
if (dataMap.value("status").toString() == "unauthorized") {
qWarning() << "Something's off with the token";
@ -767,6 +771,18 @@ void JsonRpcClient::helloReply(int /*commandId*/, const QVariantMap &params)
// qDebug() << "Caches:" << m_cacheHashes;
if (m_jsonRpcVersion.majorVersion() >= 6 && m_authenticationRequired) {
if (!params.value("authenticated").toBool()) {
qCWarning(dcJsonRpc) << "Seems our token is not valid!";
m_token.clear();
QSettings settings;
settings.beginGroup("jsonTokens");
settings.setValue(m_serverUuid, m_token);
settings.endGroup();
emit authenticationRequiredChanged();
m_authenticated = false;
emit authenticatedChanged();
return;
}
m_permissionScopes = UserInfo::listToScopes(params.value("permissionScopes").toStringList());
} else {
m_permissionScopes = UserInfo::PermissionScopeAdmin;

View File

@ -116,7 +116,7 @@ public:
Q_INVOKABLE bool ensureServerVersion(const QString &jsonRpcVersion);
Q_INVOKABLE int createUser(const QString &username, const QString &password);
Q_INVOKABLE int createUser(const QString &username, const QString &password, const QString &displayName, const QString &email);
Q_INVOKABLE int authenticate(const QString &username, const QString &password, const QString &deviceName);
Q_INVOKABLE int requestPushButtonAuth(const QString &deviceName);
Q_INVOKABLE int setupRemoteAccess(const QString &idToken, const QString &userId);

View File

@ -8,6 +8,11 @@
class TokenInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QUuid id READ id CONSTANT)
Q_PROPERTY(QString username READ username CONSTANT)
Q_PROPERTY(QString deviceName READ deviceName CONSTANT)
Q_PROPERTY(QDateTime creationTime READ creationTime CONSTANT)
public:
explicit TokenInfo(const QUuid &id, const QString &username, const QString &deviceName, const QDateTime &creationTime, QObject *parent = nullptr);

View File

@ -58,3 +58,11 @@ void TokenInfos::removeToken(const QUuid &tokenId)
}
}
}
TokenInfo *TokenInfos::get(int index) const
{
if (index < 0 || index >= m_list.count()) {
return nullptr;
}
return m_list.at(index);
}

View File

@ -26,6 +26,8 @@ public:
void addToken(TokenInfo *tokenInfo);
void removeToken(const QUuid &tokenId);
Q_INVOKABLE TokenInfo* get(int index) const;
signals:
void countChanged();

View File

@ -29,6 +29,32 @@ void UserInfo::setUsername(const QString &username)
}
}
QString UserInfo::email() const
{
return m_email;
}
void UserInfo::setEmail(const QString &email)
{
if (m_email != email) {
m_email = email;
emit emailChanged();
}
}
QString UserInfo::displayName() const
{
return m_displayName;
}
void UserInfo::setDisplayName(const QString &displayName)
{
if (m_displayName != displayName) {
m_displayName = displayName;
emit displayNameChanged();
}
}
UserInfo::PermissionScopes UserInfo::scopes() const
{
return m_scopes;

View File

@ -7,6 +7,8 @@ class UserInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QString username READ username NOTIFY usernameChanged)
Q_PROPERTY(QString email READ email NOTIFY emailChanged)
Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
Q_PROPERTY(PermissionScopes scopes READ scopes NOTIFY scopesChanged)
public:
enum PermissionScope {
@ -26,6 +28,12 @@ public:
QString username() const;
void setUsername(const QString &username);
QString email() const;
void setEmail(const QString &email);
QString displayName() const;
void setDisplayName(const QString &displayName);
PermissionScopes scopes() const;
void setScopes(PermissionScopes scopes);
@ -34,10 +42,14 @@ public:
signals:
void usernameChanged();
void emailChanged();
void displayNameChanged();
void scopesChanged();
private:
QString m_username;
QString m_email;
QString m_displayName;
PermissionScopes m_scopes = PermissionScopeNone;
};

View File

@ -70,12 +70,15 @@ Users *UserManager::users() const
return m_users;
}
int UserManager::createUser(const QString &username, const QString &password, int permissionScopes)
int UserManager::createUser(const QString &username, const QString &password, const QString &displayName, const QString &email, int permissionScopes)
{
QVariantMap params;
params.insert("username", username);
params.insert("password", password);
if (m_engine->jsonRpcClient()->ensureServerVersion("5.6")) {
if (m_engine->jsonRpcClient()->ensureServerVersion("6.0")) {
params.insert("displayName", displayName);
params.insert("email", email);
params.insert("scopes", UserInfo::scopesToList((UserInfo::PermissionScopes)permissionScopes));
}
qCDebug(dcUserManager()) << "Creating user" << username << permissionScopes;
@ -115,6 +118,16 @@ int UserManager::setUserScopes(const QString &username, int scopes)
return m_engine->jsonRpcClient()->sendCommand("Users.SetUserScopes", params, this, "setUserScopesResponse");
}
int UserManager::setUserInfo(const QString &username, const QString &displayName, const QString &email)
{
QVariantMap params;
params.insert("username", username);
params.insert("displayName", displayName);
params.insert("email", email);
qCDebug(dcUserManager()) << "Setting new info for user" << username << displayName << email;
return m_engine->jsonRpcClient()->sendCommand("Users.SetUserInfo", params, this, "setUserInfoResponse");
}
void UserManager::notificationReceived(const QVariantMap &data)
{
qCDebug(dcUserManager()) << "Users notification" << data;
@ -122,6 +135,8 @@ void UserManager::notificationReceived(const QVariantMap &data)
if (notification == "Users.UserAdded") {
QVariantMap userMap = data.value("params").toMap().value("userInfo").toMap();
UserInfo *info = new UserInfo(userMap.value("username").toString());
info->setDisplayName(userMap.value("displayName").toString());
info->setEmail(userMap.value("email").toString());
info->setScopes(UserInfo::listToScopes(userMap.value("scopes").toStringList()));
m_users->insertUser(info);
} else if (notification == "Users.UserRemoved") {
@ -129,12 +144,24 @@ void UserManager::notificationReceived(const QVariantMap &data)
} else if (notification == "Users.UserChanged") {
QVariantMap userMap = data.value("params").toMap().value("userInfo").toMap();
QString username = userMap.value("username").toString();
QString displayName = userMap.value("displayName").toString();
QString email = userMap.value("email").toString();
UserInfo::PermissionScopes scopes = UserInfo::listToScopes(userMap.value("scopes").toStringList());
// Update current user info
if (m_userInfo && m_userInfo->username() == username) {
m_userInfo->setDisplayName(displayName);
m_userInfo->setEmail(email);
m_userInfo->setScopes(scopes);
}
// Update user info in the list of all users.
UserInfo *info = m_users->getUserInfo(username);
if (!info) {
qCWarning(dcUserManager()) << "Received a change notification for a user we don't know:" << username;
return;
}
info->setScopes(UserInfo::listToScopes(userMap.value("scopes").toStringList()));
info->setDisplayName(displayName);
info->setEmail(email);
info->setScopes(scopes);
}
}
@ -145,6 +172,8 @@ void UserManager::getUsersResponse(int commandId, const QVariantMap &data)
foreach (const QVariant &userVariant, data.value("users").toList()) {
QVariantMap userMap = userVariant.toMap();
UserInfo *userInfo = new UserInfo(userMap.value("username").toString());
userInfo->setDisplayName(userMap.value("displayName").toString());
userInfo->setEmail(userMap.value("email").toString());
userInfo->setScopes(UserInfo::listToScopes(userMap.value("scopes").toStringList()));
m_users->insertUser(userInfo);
}
@ -155,6 +184,8 @@ void UserManager::getUserInfoResponse(int commandId, const QVariantMap &data)
qCDebug(dcUserManager()) << "User info reply" << commandId << data;
QVariantMap userMap = data.value("userInfo").toMap();
m_userInfo->setUsername(userMap.value("username").toString());
m_userInfo->setEmail(userMap.value("email").toString());
m_userInfo->setDisplayName(userMap.value("displayName").toString());
m_userInfo->setScopes(UserInfo::listToScopes(userMap.value("scopes").toStringList()));
}
@ -203,6 +234,9 @@ void UserManager::changePasswordResponse(int commandId, const QVariantMap &param
void UserManager::createUserResponse(int commandId, const QVariantMap &params)
{
qCDebug(dcUserManager()) << "Create user response:" << commandId << params;
QMetaEnum metaEnum = QMetaEnum::fromType<UserManager::UserError>();
UserError error = static_cast<UserError>(metaEnum.keyToValue(params.value("error").toString().toUtf8()));
emit createUserReply(commandId, error);
}
void UserManager::removeUserResponse(int commandId, const QVariantMap &params)
@ -221,6 +255,14 @@ void UserManager::setUserScopesResponse(int commandId, const QVariantMap &params
emit setUserScopesReply(commandId, error);
}
void UserManager::setUserInfoResponse(int commandId, const QVariantMap &params)
{
qCDebug(dcUserManager()) << "Set user info response:" << commandId << params;
QMetaEnum metaEnum = QMetaEnum::fromType<UserManager::UserError>();
UserError error = static_cast<UserError>(metaEnum.keyToValue(params.value("error").toString().toUtf8()));
emit setUserInfoReply(commandId, error);
}
Users::Users(QObject *parent): QAbstractListModel(parent)
{

View File

@ -46,21 +46,24 @@ public:
Users *users() const;
// NOTE: Q_FLAG from another QObject (UserInfo::PermissionScopes) doesn't seem to work in certain Qt versions. Using int instead
Q_INVOKABLE int createUser(const QString &username, const QString &password, int permissionScopes = UserInfo::PermissionScopeAdmin);
Q_INVOKABLE int createUser(const QString &username, const QString &password, const QString &displayName, const QString &email, int permissionScopes = UserInfo::PermissionScopeAdmin);
Q_INVOKABLE int changePassword(const QString &newPassword);
Q_INVOKABLE int removeToken(const QUuid &id);
Q_INVOKABLE int removeUser(const QString &username);
// NOTE: Q_FLAG from another QObject (UserInfo::PermissionScopes) doesn't seem to work in certain Qt versions. Using int instead
Q_INVOKABLE int setUserScopes(const QString &username, int permissionScopes);
Q_INVOKABLE int setUserInfo(const QString &username, const QString &displayName, const QString &email);
signals:
void engineChanged();
void loadingChanged();
void createUserReply(int id, UserError error);
void removeTokenReply(int id, UserError error);
void changePasswordReply(int id, UserError error);
void removeUserReply(int id, UserError error);
void setUserScopesReply(int id, UserError error);
void setUserInfoReply(int id, UserError error);
private slots:
void notificationReceived(const QVariantMap &data);
@ -73,6 +76,7 @@ private slots:
void createUserResponse(int commandId, const QVariantMap &params);
void removeUserResponse(int commandId, const QVariantMap &params);
void setUserScopesResponse(int commandId, const QVariantMap &params);
void setUserInfoResponse(int commandId, const QVariantMap &params);
private:
Engine *m_engine = nullptr;

View File

@ -270,5 +270,6 @@
<file>ui/images/sensors/orp.svg</file>
<file>ui/images/sensors/co.svg</file>
<file>ui/images/sensors/gas.svg</file>
<file>ui/images/contact-group.svg</file>
</qresource>
</RCC>

View File

@ -273,5 +273,6 @@
<file>ui/mainviews/energy/ConsumersPieChart.qml</file>
<file>ui/components/NymeaToolTip.qml</file>
<file>ui/components/SettingsTile.qml</file>
<file>ui/components/NymeaTextField.qml</file>
</qresource>
</RCC>

View File

@ -140,7 +140,7 @@ Drawer {
text: qsTr("Configure things")
iconName: "../images/things.svg"
visible: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost
&& NymeaUtils.hasPermissionScope(root.currentEngine, UserInfo.PermissionScopeConfigureThings)
&& NymeaUtils.hasPermissionScope(root.currentEngine.jsonRpcClient.permissions, UserInfo.PermissionScopeConfigureThings)
&& root.currentEngine.jsonRpcClient.connected
progressive: false
onClicked: {
@ -154,7 +154,7 @@ Drawer {
iconName: "../images/magic.svg"
progressive: false
visible: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost
&& NymeaUtils.hasPermissionScope(root.currentEngine, UserInfo.PermissionScopeConfigureRules)
&& NymeaUtils.hasPermissionScope(root.currentEngine.jsonRpcClient.permissions, UserInfo.PermissionScopeConfigureRules)
&& root.currentEngine.jsonRpcClient.connected && Configuration.magicEnabled
onClicked: {
root.openMagicSettings();

View File

@ -61,7 +61,7 @@ Page {
iconSource: "../images/configure.svg"
text: qsTr("General")
subText: qsTr("Change system name and time zone")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin)
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
onClicked: pageStack.push(Qt.resolvedUrl("system/GeneralSettingsPage.qml"))
}
@ -80,7 +80,7 @@ Page {
iconSource: "../images/connections/network-wifi.svg"
text: qsTr("Networking")
subText: qsTr("Configure the system's network connection")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin) && Configuration.networkSettingsEnabled
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.networkSettingsEnabled
onClicked: pageStack.push(Qt.resolvedUrl("system/NetworkSettingsPage.qml"))
}
@ -89,7 +89,7 @@ Page {
iconSource: "../images/connections/cloud.svg"
text: qsTr("Cloud")
subText: qsTr("Connect this %1 system to %1:cloud").arg(Configuration.systemName)
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin)
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
onClicked: pageStack.push(Qt.resolvedUrl("system/CloudSettingsPage.qml"))
}
@ -98,7 +98,7 @@ Page {
iconSource: "../images/connections/network-vpn.svg"
text: qsTr("API interfaces")
subText: qsTr("Configure how clients interact with this system")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin) && Configuration.apiSettingsEnabled
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.apiSettingsEnabled
onClicked: pageStack.push(Qt.resolvedUrl("system/ConnectionInterfacesPage.qml"))
}
@ -107,7 +107,7 @@ Page {
iconSource: "../images/mqtt.svg"
text: qsTr("MQTT broker")
subText: qsTr("Configure the MQTT broker")
visible: engine.jsonRpcClient.ensureServerVersion("1.11") && NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin) && Configuration.mqttSettingsEnabled
visible: engine.jsonRpcClient.ensureServerVersion("1.11") && NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.mqttSettingsEnabled
onClicked: pageStack.push(Qt.resolvedUrl("system/MqttBrokerSettingsPage.qml"))
}
@ -116,7 +116,7 @@ Page {
iconSource: "../images/stock_website.svg"
text: qsTr("Web server")
subText: qsTr("Configure the web server")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin)
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
&& Configuration.webServerSettingsEnabled
onClicked: pageStack.push(Qt.resolvedUrl("system/WebServerSettingsPage.qml"))
}
@ -126,7 +126,7 @@ Page {
iconSource: "../images/zigbee.svg"
text: qsTr("ZigBee")
subText: qsTr("Configure ZigBee networks")
visible: engine.jsonRpcClient.ensureServerVersion("5.3") && NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin) && Configuration.zigbeeSettingsEnabled
visible: engine.jsonRpcClient.ensureServerVersion("5.3") && NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.zigbeeSettingsEnabled
onClicked: pageStack.push(Qt.resolvedUrl("system/ZigbeeSettingsPage.qml"))
}
@ -135,7 +135,7 @@ Page {
iconSource: "../images/modbus.svg"
text: qsTr("Modbus RTU")
subText: qsTr("Configure Modbus RTU master interfaces")
visible: engine.jsonRpcClient.ensureServerVersion("5.6") && NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin) && Configuration.modbusSettingsEnabled
visible: engine.jsonRpcClient.ensureServerVersion("5.6") && NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.modbusSettingsEnabled
onClicked: pageStack.push(Qt.resolvedUrl("system/ModbusRtuSettingsPage.qml"))
}
@ -144,7 +144,7 @@ Page {
iconSource: "../images/plugin.svg"
text: qsTr("Plugins")
subText: qsTr("List and cofigure installed plugins")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin) && Configuration.pluginSettingsEnabled
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && Configuration.pluginSettingsEnabled
onClicked:pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml"))
}
@ -153,7 +153,7 @@ Page {
iconSource: "../images/sdk.svg"
text: qsTr("Developer tools")
subText: qsTr("Access tools for debugging and error reporting")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin)
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
onClicked: pageStack.push(Qt.resolvedUrl("system/DeveloperTools.qml"))
}
@ -163,7 +163,7 @@ Page {
text: qsTr("System update")
subText: qsTr("Update your %1 system").arg(Configuration.systemName)
visible: engine.systemController.updateManagementAvailable &&
NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin)
NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
onClicked: pageStack.push(Qt.resolvedUrl("system/SystemUpdatePage.qml"))
}
@ -172,7 +172,7 @@ Page {
iconSource: "../images/logs.svg"
text: qsTr("Log viewer")
subText: qsTr("View system log")
visible: NymeaUtils.hasPermissionScope(engine, UserInfo.PermissionScopeAdmin)
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
onClicked: pageStack.push(Qt.resolvedUrl("system/LogViewerPage.qml"))
}

View File

@ -0,0 +1,19 @@
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtQuick.Controls.Material 2.0
import Nymea 1.0
TextField {
id: control
property bool error: false
background: Rectangle {
y: control.height - height - control.bottomPadding + 8
implicitWidth: 120
height: control.activeFocus || control.hovered ? 2 : 1
color: control.error ? Style.red : control.activeFocus ? Style.accentColor
: (control.hovered ? control.Material.primaryTextColor : control.Material.hintTextColor)
}
}

View File

@ -65,15 +65,19 @@ ColumnLayout {
property bool hiddenPassword: true
property bool showErrors: false
RowLayout {
Layout.fillWidth: true
TextField {
NymeaTextField {
id: passwordTextField
Layout.fillWidth: true
echoMode: root.hiddenPassword ? TextInput.Password : TextInput.Normal
placeholderText: root.signup ? qsTr("Pick a password") : qsTr("Password")
error: root.showErrors && !root.isValidPassword
palette.toolTipBase: Style.tooltipBackgroundColor
ToolTip.visible: root.signup && focus && !root.isValidPassword
ToolTip.delay: 1000
ToolTip.onVisibleChanged: print("Tooltip visible changed:", ToolTip.visible, focus, root.isValidPassword)
@ -100,7 +104,7 @@ ColumnLayout {
}
var ret = []
for (var i = 0; i < texts.length; i++) {
var entry = "<font color=\"%1\">• ".arg(checks[i] ? "#ffffff" : Style.accentColor)
var entry = "<font color=\"%1\">• ".arg(checks[i] ? "#ffffff" : Style.red)
entry += texts[i]
entry += "</font>"
ret.push(entry)
@ -126,11 +130,12 @@ ColumnLayout {
RowLayout {
visible: root.signup
TextField {
NymeaTextField {
id: confirmationPasswordTextField
Layout.fillWidth: true
echoMode: root.hiddenPassword ? TextInput.Password : TextInput.Normal
placeholderText: qsTr("Confirm password")
error: root.showErrors && (!root.isValidPassword || !root.confirmationMatches)
}
}
}

View File

@ -34,7 +34,7 @@ import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
Page {
SettingsPageBase {
id: root
signal backPressed();
@ -82,85 +82,116 @@ Page {
}
}
Flickable {
anchors.fill: parent
contentHeight: contentColumn.implicitHeight
ColumnLayout {
id: contentColumn
width: parent.width
ColumnLayout {
id: contentColumn
width: parent.width
spacing: app.margins
RowLayout {
Layout.margins: app.margins
spacing: app.margins
RowLayout {
Layout.margins: app.margins
spacing: app.margins
ColorIcon {
Layout.preferredHeight: Style.iconSize * 2
Layout.preferredWidth: Style.iconSize * 2
name: "../images/lock-closed.svg"
color: Style.accentColor
}
Label {
Layout.fillWidth: true
text: engine.jsonRpcClient.initialSetupRequired ?
qsTr("In order to use your %1 system, please create an account.").arg(Configuration.systemName)
: qsTr("In order to use your %1 system, please log in.").arg(Configuration.systemName)
wrapMode: Text.WordWrap
}
ColorIcon {
Layout.preferredHeight: Style.iconSize * 2
Layout.preferredWidth: Style.iconSize * 2
name: "../images/lock-closed.svg"
color: Style.accentColor
}
GridLayout {
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
columns: app.width > 500 ? 2 : 1
columnSpacing: app.margins
text: engine.jsonRpcClient.initialSetupRequired ?
qsTr("In order to use your %1 system, please create an account.").arg(Configuration.systemName)
: qsTr("In order to use your %1 system, please log in.").arg(Configuration.systemName)
wrapMode: Text.WordWrap
}
}
Label {
text: engine.jsonRpcClient.ensureServerVersion("7.0") ? qsTr("Username:") : qsTr("Your e-mail address:")
Layout.fillWidth: true
Layout.minimumWidth: implicitWidth
}
TextField {
id: usernameTextField
Layout.fillWidth: true
inputMethodHints: engine.jsonRpcClient.ensureServerVersion("7.0")
? Qt.ImhEmailCharactersOnly | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
// placeholderText: "john.smith@cooldomain.com"
}
Label {
Layout.fillWidth: true
text: qsTr("Password:")
}
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
minPasswordLength: 8
requireLowerCaseLetter: true
requireUpperCaseLetter: true
requireNumber: true
requireSpecialChar: false
signup: engine.jsonRpcClient.initialSetupRequired
GridLayout {
id: loginForm
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
columns: app.width > 500 ? 2 : 1
columnSpacing: app.margins
property bool showErrors: false
Label {
text: (engine.jsonRpcClient.ensureServerVersion("6.0") ? qsTr("Username") : qsTr("Your e-mail address"))
Layout.fillWidth: true
Layout.minimumWidth: implicitWidth
}
NymeaTextField {
id: usernameTextField
Layout.fillWidth: true
placeholderText: qsTr("Required")
inputMethodHints: engine.jsonRpcClient.ensureServerVersion("6.0")
? Qt.ImhEmailCharactersOnly | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
error: loginForm.showErrors && !acceptableInput
validator: RegExpValidator {
regExp: /[a-zA-Z0-9_\\.+-@]{3,}/
}
}
Button {
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
text: qsTr("OK")
enabled: usernameTextField.displayText.length >= 3 && passwordTextField.isValid
onClicked: {
if (engine.jsonRpcClient.initialSetupRequired) {
print("create user")
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 (" + PlatformHelper.deviceModel + ")");
}
text: qsTr("Password")
}
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
minPasswordLength: 8
requireLowerCaseLetter: true
requireUpperCaseLetter: true
requireNumber: true
requireSpecialChar: false
signup: engine.jsonRpcClient.initialSetupRequired
showErrors: loginForm.showErrors
}
Label {
text: qsTr("Your name")
Layout.fillWidth: true
visible: engine.jsonRpcClient.ensureServerVersion("6.0") && engine.jsonRpcClient.initialSetupRequired
}
TextField {
id: displayNameTextField
Layout.fillWidth: true
placeholderText: qsTr("Optional")
visible: engine.jsonRpcClient.ensureServerVersion("6.0") && engine.jsonRpcClient.initialSetupRequired
}
Label {
text: qsTr("Email")
Layout.fillWidth: true
visible: engine.jsonRpcClient.ensureServerVersion("6.0") && engine.jsonRpcClient.initialSetupRequired
}
TextField {
id: emailTextField
Layout.fillWidth: true
placeholderText: qsTr("Optional")
visible: engine.jsonRpcClient.ensureServerVersion("6.0") && engine.jsonRpcClient.initialSetupRequired
}
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins
text: qsTr("OK")
onClicked: {
loginForm.showErrors = true
if (!usernameTextField.acceptableInput || !passwordTextField.isValid) {
return;
}
if (engine.jsonRpcClient.initialSetupRequired) {
print("create user")
engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.password, displayNameTextField.text, emailTextField.text);
} else {
print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app")
engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.password, "nymea-app (" + PlatformHelper.deviceModel + ")");
}
}
}

View File

@ -272,11 +272,13 @@ ThingPageBase {
target: stateDelegateLoader.item
property: "from"
value: stateDelegate.thingState.minValue
when: stateDelegateLoader.item.hasOwnProperty("from")
}
Binding {
target: stateDelegateLoader.item
property: "to"
value: stateDelegate.thingState.maxValue
when: stateDelegateLoader.item.hasOwnProperty("to")
}
Binding {
target: stateDelegateLoader.item.hasOwnProperty("unit") ? stateDelegateLoader.item : null

View File

@ -0,0 +1,193 @@
<?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.92pre1 unknown"
viewBox="0 0 96 96.000001"
sodipodi:docname="contact-group.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="10.976561"
inkscape:cx="-15.533103"
inkscape:cy="56.488537"
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="false"
inkscape:snap-smooth-nodes="false"
inkscape:snap-midpoints="false"
inkscape:snap-object-midpoints="false"
inkscape:snap-center="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-others="false">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="92,-8.0000001"
id="guide4075"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076"
inkscape:locked="false" />
<sodipodi:guide
orientation="0,1"
position="-5,12"
id="guide4078"
inkscape:locked="false" />
<sodipodi:guide
orientation="1,0"
position="84,-9.0000001"
id="guide4080"
inkscape:locked="false" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170"
inkscape:locked="false" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 405.98047,360.83398 c -2.92356,0 -5.62655,0.43591 -8.07617,1.33985 -2.41608,0.89153 -4.54478,2.18046 -6.32813,3.85156 -1.78862,1.63101 -3.19224,3.61247 -4.17578,5.88086 l -0.008,0.0156 -0.008,0.0176 c -0.94047,2.28314 -1.40235,4.76367 -1.40235,7.38477 0,2.62104 0.46188,5.10162 1.40235,7.38476 l 0.008,0.0176 0.008,0.0156 c 0.98234,2.2656 2.37795,4.26268 4.15625,5.93359 l 0.0117,0.0117 0.0117,0.01 c 1.78511,1.62648 3.91161,2.88707 6.32422,3.77734 2.44963,0.90396 5.15261,1.33984 8.07617,1.33984 2.87838,0 5.55748,-0.43632 8,-1.33593 l 0.004,-0.002 c 2.4549,-0.88866 4.60754,-2.14753 6.39843,-3.77929 l 0.0117,-0.01 0.0117,-0.0117 c 1.78118,-1.67362 3.15878,-3.6796 4.09766,-5.95312 0.98771,-2.28529 1.47656,-4.77228 1.47656,-7.39844 0,-2.62622 -0.48885,-5.11316 -1.47656,-7.39844 -0.93998,-2.27607 -2.32569,-4.2667 -4.11719,-5.90039 -1.78918,-1.67646 -3.9442,-2.96367 -6.40234,-3.85351 -2.44359,-0.90059 -5.12398,-1.3379 -8.00391,-1.3379 z m 0,4.00196 c 2.47948,0 4.67711,0.37229 6.62695,1.09179 l 0.006,0.002 0.006,0.002 c 2.01547,0.72913 3.67584,1.73514 5.04688,3.02344 l 0.0117,0.0117 0.01,0.01 c 1.36676,1.24529 2.39573,2.71587 3.125,4.48633 l 0.006,0.0176 0.008,0.0176 c 0.76507,1.76442 1.1543,3.68375 1.1543,5.82617 0,2.14234 -0.38922,4.06367 -1.1543,5.82812 l -0.008,0.0156 -0.006,0.0176 c -0.72853,1.76865 -1.76088,3.2675 -3.13281,4.56054 -1.37137,1.24771 -3.0374,2.23879 -5.06055,2.97071 l -0.006,0.002 -0.006,0.002 c -1.94984,0.71952 -4.14747,1.09375 -6.62695,1.09375 -2.52668,0 -4.74565,-0.37573 -6.69141,-1.09375 -1.97691,-0.72949 -3.62487,-1.71902 -5.00195,-2.9707 -1.37663,-1.29629 -2.4359,-2.80476 -3.20899,-4.58203 -0.72519,-1.7671 -1.0957,-3.69599 -1.0957,-5.84375 0,-2.14575 0.37191,-4.07222 1.0957,-5.83789 0.77314,-1.77839 1.82515,-3.26139 3.19532,-4.50977 l 0.0117,-0.01 0.0117,-0.0117 c 1.37779,-1.29463 3.02071,-2.29987 4.99218,-3.02735 1.94576,-0.718 4.16472,-1.09179 6.69141,-1.09179 z"
id="path4202"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 355.93359,345.47266 -0.002,4 c 4.08212,0.003 7.36661,0.32537 9.9043,1.11914 2.53769,0.79376 4.32832,1.98431 5.7832,3.94531 2.90977,3.92201 4.35156,11.50475 4.35156,24.77344 0,13.26868 -1.44098,20.85138 -4.35156,24.77734 -1.45529,1.96298 -3.24695,3.15589 -5.78516,3.95313 -2.5382,0.79723 -5.82397,1.12543 -9.90625,1.13476 l 0.01,4 c 4.2821,-0.01 7.93327,-0.3231 11.0957,-1.3164 3.16244,-0.99331 5.83546,-2.74036 7.79883,-5.38868 3.92675,-5.29662 5.14063,-13.62724 5.14063,-27.16015 -1e-5,-13.53292 -1.21307,-21.86238 -5.14063,-27.15625 -1.96378,-2.64694 -4.63783,-4.39152 -7.80078,-5.38086 -3.16295,-0.98934 -6.8154,-1.29771 -11.09766,-1.30078 z"
id="path4228"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 400.2832,401.98438 c -2.21241,0 -4.27231,0.32902 -6.14843,1.02148 -1.84263,0.68006 -3.47923,1.6698 -4.84961,2.95312 -1.37567,1.25503 -2.4588,2.78386 -3.21289,4.52344 l -0.008,0.0156 -0.006,0.0176 c -0.72247,1.75433 -1.07617,3.65801 -1.07617,5.65626 0,1.9982 0.3537,3.90387 1.07617,5.6582 l 0.006,0.0176 0.008,0.0156 c 0.75289,1.7368 1.82802,3.27543 3.19335,4.5586 l 0.0117,0.0117 0.0117,0.01 c 1.37215,1.25048 3.00661,2.2177 4.84571,2.89648 1.87612,0.69248 3.93602,1.02149 6.14843,1.02149 2.17875,0 4.22087,-0.32947 6.08985,-1.01758 l 0.004,-0.002 c 1.86995,-0.67719 3.52443,-1.64269 4.90235,-2.89844 l 0.01,-0.01 0.0117,-0.0117 c 1.36567,-1.28349 2.42759,-2.82758 3.14844,-4.56836 0.76088,-1.75896 1.13672,-3.67481 1.13672,-5.68164 0,-2.00688 -0.37583,-3.92073 -1.13672,-5.67969 -0.72126,-1.74157 -1.78811,-3.27462 -3.16211,-4.5293 -1.3772,-1.29102 -3.03459,-2.27975 -4.91016,-2.95898 -1.87004,-0.68909 -3.91346,-1.01954 -6.09375,-1.01953 z m 0,4 c 1.77973,0 3.34054,0.26742 4.7168,0.77539 l 0.006,0.002 0.006,0.002 c 1.43042,0.51758 2.59072,1.22452 3.54883,2.125 l 0.0117,0.01 0.0117,0.0117 c 0.95384,0.86924 1.66639,1.8872 2.17774,3.1289 l 0.006,0.0176 0.008,0.0156 c 0.53569,1.23567 0.81055,2.57994 0.81055,4.09961 0,1.51961 -0.27486,2.86587 -0.81055,4.10156 l -0.008,0.0156 -0.006,0.0176 c -0.51112,1.24114 -1.231,2.28172 -2.19141,3.1875 -0.95773,0.87003 -2.12248,1.56823 -3.55859,2.08789 l -0.006,0.002 -0.006,0.002 c -1.37626,0.50797 -2.93708,0.77539 -4.7168,0.77539 -1.81545,0 -3.39149,-0.26892 -4.76367,-0.77539 -1.40473,-0.51847 -2.55867,-1.2168 -3.52344,-2.09375 -0.9613,-0.90678 -1.69723,-1.95588 -2.24023,-3.20117 -0.50811,-1.23918 -0.77344,-2.59297 -0.77344,-4.11914 0,-1.52623 0.26533,-2.87804 0.77344,-4.11719 0.54334,-1.24582 1.27338,-2.2741 2.22852,-3.14453 l 0.01,-0.0117 0.0117,-0.01 c 0.96485,-0.90682 2.11577,-1.61298 3.51367,-2.1289 1.37219,-0.50646 2.94822,-0.7754 4.76367,-0.77539 z"
id="path4269"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu, Normal';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 375.9375,416.16406 c 0,9.64488 -1.05932,15.07099 -3.05273,17.74219 -0.99671,1.3356 -2.19811,2.13401 -4.00586,2.69141 -1.80776,0.5574 -4.21783,0.79793 -7.25782,0.80468 h -5.6914 v 4 h 5.69726 0.004 c 3.23429,-0.007 6.00035,-0.23397 8.42773,-0.98242 2.42738,-0.74845 4.5202,-2.09364 6.03321,-4.12109 3.02601,-4.0549 3.84765,-10.23305 3.84765,-20.13477 z"
id="path4271"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -14,7 +14,7 @@ SettingsPageBase {
engine: _engine
onChangePasswordReply: {
if (error != UserManager.UserErrorNoError) {
if (error !== UserManager.UserErrorNoError) {
var component = Qt.createComponent("../components/ErrorDialog.qml")
var text;
switch (error) {
@ -36,22 +36,37 @@ SettingsPageBase {
popup.open()
}
}
}
SettingsPageSectionHeader {
text: qsTr("User info")
RowLayout {
Layout.margins: Style.margins
spacing: Style.margins
ColorIcon {
size: Style.hugeIconSize
source: "../images/account.svg"
color: Style.accentColor
}
ColumnLayout {
Label {
Layout.fillWidth: true
text: userManager.userInfo.displayName || userManager.userInfo.username
font: Style.bigFont
}
Label {
Layout.fillWidth: true
text: userManager.userInfo.username
visible: userManager.userInfo.displayName !== ""
}
Label {
Layout.fillWidth: true
text: userManager.userInfo.email
font: Style.smallFont
}
}
}
NymeaSwipeDelegate {
Layout.fillWidth: true
text: userManager.userInfo.username
subText: qsTr("Username")
progressive: false
prominentSubText: false
iconName: "../images/account.svg"
}
NymeaSwipeDelegate {
NymeaItemDelegate {
Layout.fillWidth: true
text: qsTr("Change password")
iconName: "../images/key.svg"
@ -63,15 +78,17 @@ SettingsPageBase {
}
}
SettingsPageSectionHeader {
text: qsTr("Device access")
NymeaItemDelegate {
Layout.fillWidth: true
text: qsTr("Edit user information")
iconName: "../images/edit.svg"
onClicked: pageStack.push(editUserInfoComponent)
}
Button {
NymeaItemDelegate {
Layout.fillWidth: true
Layout.leftMargin: Style.margins
Layout.rightMargin: Style.margins
text: qsTr("Manage authorized devices")
iconName: "../images/smartphone.svg"
onClicked: {
pageStack.push(manageTokensComponent)
}
@ -82,17 +99,67 @@ SettingsPageBase {
visible: userManager.userInfo.scopes & UserInfo.PermissionScopeAdmin
}
Button {
NymeaItemDelegate {
Layout.fillWidth: true
Layout.leftMargin: Style.margins
Layout.rightMargin: Style.margins
text: qsTr("Manage users")
visible: userManager.userInfo.scopes & UserInfo.PermissionScopeAdmin
iconName: "../images/contact-group.svg"
onClicked: {
pageStack.push(manageUsersComponent)
}
}
Component {
id: editUserInfoComponent
SettingsPageBase {
id: editUserInfoPage
title: qsTr("Edit user information")
GridLayout {
Layout.margins: Style.margins
columnSpacing: Style.margins
columns: 2
Label {
text: qsTr("Your name")
}
NymeaTextField {
id: displayNameTextField
Layout.fillWidth: true
text: userManager.userInfo.displayName
}
Label {
text: qsTr("Email")
}
NymeaTextField {
id: emailTextField
Layout.fillWidth: true
text: userManager.userInfo.email
}
}
Button {
Layout.fillWidth: true
Layout.margins: Style.margins
text: qsTr("OK")
onClicked: {
editUserInfoPage.busy = true
userManager.setUserInfo(userManager.userInfo.username, displayNameTextField.text, emailTextField.text)
}
}
Connections {
target: userManager
onSetUserInfoReply: {
editUserInfoPage.busy = false
if (error != UserManager.UserErrorNoError) {
var component = Qt.createComponent("../components/ErrorDialog.qml")
var text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error);
var popup = component.createObject(app, {text: text});
popup.open()
} else {
pageStack.pop()
}
}
}
}
}
Component {
id: changePasswordComponent
@ -146,6 +213,20 @@ SettingsPageBase {
id: manageTokensPage
title: qsTr("Device access")
Component {
id: confirmTokenDeletionComponent
MeaDialog {
headerIcon: "../images/lock-closed.svg"
title: qsTr("Remove device access")
text: qsTr("Are you sure you want to remove %1 from accessing your %2 system?").arg("<b>" + tokenInfo.deviceName + "</b>").arg(Configuration.systemName)
property TokenInfo tokenInfo: null
standardButtons: Dialog.Yes | Dialog.No
onAccepted: {
userManager.removeToken(tokenInfo.id)
}
}
}
SettingsPageSectionHeader {
text: qsTr("Devices / Apps accessing %1").arg(Configuration.systemName)
}
@ -160,9 +241,12 @@ SettingsPageBase {
prominentSubText: false
progressive: false
canDelete: true
iconName: "../images/smartphone.svg"
onClicked: deleteClicked()
onDeleteClicked: {
userManager.removeToken(model.id)
var popup = confirmTokenDeletionComponent.createObject(manageTokensPage, {tokenInfo: userManager.tokenInfos.get(index)})
popup.open()
}
}
}
@ -192,9 +276,10 @@ SettingsPageBase {
Repeater {
model: userManager.users
delegate: NymeaSwipeDelegate {
delegate: NymeaItemDelegate {
Layout.fillWidth: true
text: model.username
text: engine.jsonRpcClient.ensureServerVersion("6.0") && model.displayName ? model.displayName : model.username
subText: engine.jsonRpcClient.ensureServerVersion("6.0") && model.displayName ? model.username : ""
iconName: "/ui/images/account.svg"
iconColor: userManager.userInfo.scopes & UserInfo.PermissionScopeAdmin ? Style.accentColor : Style.iconColor
@ -202,10 +287,6 @@ SettingsPageBase {
onClicked: {
pageStack.push(userDetailsComponent, {userInfo: userManager.users.get(index)})
}
onDeleteClicked: {
userManager.removeUser(model.username)
}
}
}
}
@ -215,18 +296,59 @@ SettingsPageBase {
id: userDetailsComponent
SettingsPageBase {
id: userDetailsPage
title: qsTr("Manage user")
title: qsTr("Manage %1").arg(userInfo.username)
property UserInfo userInfo: null
SettingsPageSectionHeader {
text: qsTr("User info")
Component {
id: confirmUserDeletionComponent
MeaDialog {
headerIcon: "../images/lock-closed.svg"
title: qsTr("Remove user")
text: qsTr("Are you sure you want to remove %1 from accessing your %2 system?").arg("<b>" + userInfo.username + "</b>").arg(Configuration.systemName)
property UserInfo userInfo: null
standardButtons: Dialog.Yes | Dialog.No
onAccepted: {
userDetailsPage.busy = true
userManager.removeUser(userInfo.username)
}
}
}
NymeaItemDelegate {
SettingsPageSectionHeader {
text: qsTr("User information for %1").arg(userDetailsPage.userInfo.username)
}
GridLayout {
Layout.leftMargin: Style.margins
Layout.rightMargin: Style.margins
columnSpacing: Style.margins
columns: 2
Label {
text: qsTr("Name")
}
NymeaTextField {
id: displayNameTextField
Layout.fillWidth: true
text: userDetailsPage.userInfo.displayName
}
Label {
text: qsTr("Email")
}
NymeaTextField {
id: emailTextField
Layout.fillWidth: true
text: userDetailsPage.userInfo.email
}
}
Button {
Layout.fillWidth: true
text: userDetailsPage.userInfo.username
progressive: false
Layout.margins: Style.margins
text: qsTr("Save")
onClicked: {
userManager.setUserInfo(userDetailsPage.userInfo.username, displayNameTextField.text, emailTextField.text)
}
}
SettingsPageSectionHeader {
@ -268,7 +390,23 @@ SettingsPageBase {
Layout.rightMargin: Style.margins
text: qsTr("Remove this user")
onClicked: {
userManager.removeUser(userDetailsPage.userInfo.username)
var popup = confirmUserDeletionComponent.createObject(userDetailsPage, {userInfo: userDetailsPage.userInfo})
popup.open()
}
}
Connections {
target: userManager
onRemoveUserReply: {
userDetailsPage.busy = false
if (error !== UserManager.UserErrorNoError) {
var component = Qt.createComponent("../components/ErrorDialog.qml")
var text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error);
var popup = component.createObject(app, {text: text});
popup.open()
} else {
pageStack.pop();
}
}
}
}
@ -284,24 +422,50 @@ SettingsPageBase {
property var permissionScopes: UserInfo.PermissionScopeNone
SettingsPageSectionHeader {
text: qsTr("Login information")
text: qsTr("USer information")
}
TextField {
id: usernameTextField
Layout.fillWidth: true
Layout.leftMargin: Style.margins
Layout.rightMargin: Style.margins
placeholderText: qsTr("Username")
inputMethodHints: Qt.ImhEmailCharactersOnly | Qt.ImhNoAutoUppercase
}
PasswordTextField {
id: passwordTextField
GridLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.margins
Layout.rightMargin: Style.margins
columns: 2
Label {
text: qsTr("Username:") + "*"
}
TextField {
id: usernameTextField
Layout.fillWidth: true
inputMethodHints: Qt.ImhNoAutoUppercase
}
Label {
text: qsTr("Password:") + "*"
Layout.alignment: Qt.AlignTop
Layout.topMargin: Style.smallMargins
}
PasswordTextField {
id: passwordTextField
Layout.fillWidth: true
}
Label {
text: qsTr("Full name:")
}
TextField {
id: displayNameTextField
Layout.fillWidth: true
}
Label {
text: qsTr("e-mail:")
}
TextField {
id: emailTextField
Layout.fillWidth: true
}
}
SettingsPageSectionHeader {
text: qsTr("Permissions")
}
@ -335,7 +499,40 @@ SettingsPageBase {
Layout.rightMargin: Style.margins
enabled: usernameTextField.displayText.length >= 3 && passwordTextField.isValid
onClicked: {
userManager.createUser(usernameTextField.displayText, passwordTextField.password, createUserPage.permissionScopes)
createUserPage.busy = true
userManager.createUser(usernameTextField.displayText, passwordTextField.password, displayNameTextField.text, emailTextField.text, createUserPage.permissionScopes)
}
}
Connections {
target: userManager
onCreateUserReply: {
createUserPage.busy = false
if (error !== UserManager.UserErrorNoError) {
var component = Qt.createComponent("../components/ErrorDialog.qml")
var text;
switch (error) {
case UserManager.UserErrorInvalidUserId:
text = qsTr("The given username is not valid. It needs to be at least three characters long and not contain special characters.");
break;
case UserManager.UserErrorDuplicateUserId:
text = qsTr("The given username is already in use. Please choose a different username.");
break;
case UserManager.UserErrorBadPassword:
text = qsTr("The given password is not valid.");
break;
case UserManager.UserErrorPermissionDenied:
text = qsTr("Permission denied.");
break;
default:
text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error);
break;
}
var popup = component.createObject(app, {text: text});
popup.open()
} else {
pageStack.pop();
}
}
}
}

View File

@ -142,7 +142,7 @@ Item {
ListElement { text: qsTr("Configure magic"); scope: UserInfo.PermissionScopeConfigureRules; resetOnUnset: UserInfo.PermissionScopeExecuteRules }
}
function hasPermissionScope(engine, requestedScope) {
return (engine.jsonRpcClient.permissions & requestedScope) === requestedScope;
function hasPermissionScope(permissions, requestedScope) {
return (permissions & requestedScope) === requestedScope;
}
}