541 lines
19 KiB
QML
541 lines
19 KiB
QML
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 NymeaApp.Utils 1.0
|
|
import "../components"
|
|
|
|
SettingsPageBase {
|
|
id: root
|
|
title: qsTr("User settings")
|
|
|
|
UserManager {
|
|
id: userManager
|
|
engine: _engine
|
|
|
|
onChangePasswordReply: {
|
|
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()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.margins: Style.margins
|
|
spacing: Style.margins
|
|
visible: !engine.jsonRpcClient.pushButtonAuthAvailable
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
NymeaItemDelegate {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Change password")
|
|
iconName: "../images/key.svg"
|
|
visible: !engine.jsonRpcClient.pushButtonAuthAvailable
|
|
onClicked: {
|
|
var page = pageStack.push(changePasswordComponent)
|
|
page.confirmed.connect(function(newPassword) {
|
|
userManager.changePassword(newPassword)
|
|
})
|
|
}
|
|
}
|
|
|
|
NymeaItemDelegate {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Edit user information")
|
|
iconName: "../images/edit.svg"
|
|
onClicked: pageStack.push(editUserInfoComponent)
|
|
visible: !engine.jsonRpcClient.pushButtonAuthAvailable
|
|
}
|
|
|
|
NymeaItemDelegate {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Manage authorized devices")
|
|
iconName: "../images/smartphone.svg"
|
|
onClicked: {
|
|
pageStack.push(manageTokensComponent)
|
|
}
|
|
}
|
|
|
|
SettingsPageSectionHeader {
|
|
text: qsTr("Admin")
|
|
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && !engine.jsonRpcClient.pushButtonAuthAvailable
|
|
}
|
|
|
|
NymeaItemDelegate {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Manage users")
|
|
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && !engine.jsonRpcClient.pushButtonAuthAvailable
|
|
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
|
|
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
|
|
signup: true
|
|
}
|
|
|
|
Button {
|
|
text: qsTr("OK")
|
|
Layout.fillWidth: true
|
|
Layout.margins: app.margins
|
|
enabled: passwordTextField.isValid
|
|
onClicked: {
|
|
changePasswordPage.confirmed(passwordTextField.password)
|
|
pageStack.pop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: manageTokensComponent
|
|
SettingsPageBase {
|
|
id: manageTokensPage
|
|
title: qsTr("Device access")
|
|
|
|
Component {
|
|
id: confirmTokenDeletionComponent
|
|
NymeaDialog {
|
|
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)
|
|
}
|
|
|
|
Repeater {
|
|
model: userManager.tokenInfos
|
|
|
|
delegate: NymeaSwipeDelegate {
|
|
Layout.fillWidth: true
|
|
text: model.deviceName
|
|
subText: qsTr("Created on %1").arg(Qt.formatDateTime(model.creationTime, Qt.DefaultLocaleShortDate))
|
|
prominentSubText: false
|
|
progressive: false
|
|
canDelete: true
|
|
iconName: "../images/smartphone.svg"
|
|
|
|
onClicked: deleteClicked()
|
|
onDeleteClicked: {
|
|
var popup = confirmTokenDeletionComponent.createObject(manageTokensPage, {tokenInfo: userManager.tokenInfos.get(index)})
|
|
popup.open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: manageUsersComponent
|
|
SettingsPageBase {
|
|
id: manageUsersPage
|
|
|
|
header: NymeaHeader {
|
|
text: qsTr("Users")
|
|
onBackPressed: pageStack.pop()
|
|
|
|
HeaderButton {
|
|
imageSource: Qt.resolvedUrl("../images/add.svg")
|
|
onClicked: {
|
|
pageStack.push(addUserComponent)
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsPageSectionHeader {
|
|
text: qsTr("Manage users for this %1 system").arg(Configuration.systemName)
|
|
}
|
|
|
|
Repeater {
|
|
model: userManager.users
|
|
delegate: NymeaItemDelegate {
|
|
Layout.fillWidth: true
|
|
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
|
|
|
|
canDelete: true
|
|
onClicked: {
|
|
pageStack.push(userDetailsComponent, {userInfo: userManager.users.get(index)})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: userDetailsComponent
|
|
SettingsPageBase {
|
|
id: userDetailsPage
|
|
title: qsTr("Manage %1").arg(userInfo.username)
|
|
|
|
property UserInfo userInfo: null
|
|
|
|
Component {
|
|
id: confirmUserDeletionComponent
|
|
NymeaDialog {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
Layout.margins: Style.margins
|
|
text: qsTr("Save")
|
|
onClicked: {
|
|
userManager.setUserInfo(userDetailsPage.userInfo.username, displayNameTextField.text, emailTextField.text)
|
|
}
|
|
}
|
|
|
|
SettingsPageSectionHeader {
|
|
text: qsTr("Permissions")
|
|
}
|
|
|
|
Repeater {
|
|
model: NymeaUtils.scopesModel
|
|
|
|
delegate: CheckDelegate {
|
|
Layout.fillWidth: true
|
|
text: model.text
|
|
checked: (userDetailsPage.userInfo.scopes & model.scope) === model.scope
|
|
enabled: model.scope === UserInfo.PermissionScopeAdmin ||
|
|
((userDetailsPage.userInfo.scopes & UserInfo.PermissionScopeAdmin) !== UserInfo.PermissionScopeAdmin)
|
|
onClicked: {
|
|
print("scopes:", userDetailsPage.userInfo.scopes)
|
|
var scopes = userDetailsPage.userInfo.scopes
|
|
if (checked) {
|
|
scopes |= model.scope
|
|
} else {
|
|
scopes &= ~model.scope
|
|
scopes |= model.resetOnUnset
|
|
}
|
|
print("username:", userDetailsPage.userInfo.username)
|
|
print("new scopes:", scopes, UserInfo.PermissionScopeAdmin)
|
|
userManager.setUserScopes(userDetailsPage.userInfo.username, scopes)
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsPageSectionHeader {
|
|
text: qsTr("Remove")
|
|
}
|
|
|
|
Button {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins
|
|
Layout.rightMargin: Style.margins
|
|
text: qsTr("Remove this user")
|
|
onClicked: {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: addUserComponent
|
|
|
|
SettingsPageBase {
|
|
id: createUserPage
|
|
title: qsTr("Add a user")
|
|
|
|
property var permissionScopes: UserInfo.PermissionScopeNone
|
|
|
|
SettingsPageSectionHeader {
|
|
text: qsTr("User information")
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
|
|
Repeater {
|
|
id: scopesRepeater
|
|
model: NymeaUtils.scopesModel
|
|
|
|
delegate: CheckDelegate {
|
|
Layout.fillWidth: true
|
|
text: model.text
|
|
checked: (createUserPage.permissionScopes & model.scope) === model.scope
|
|
onClicked: {
|
|
var scopes = createUserPage.permissionScopes
|
|
if (checked) {
|
|
scopes |= model.scope
|
|
} else {
|
|
scopes &= ~model.scope
|
|
scopes |= model.resetOnUnset
|
|
}
|
|
createUserPage.permissionScopes = scopes
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: qsTr("Create new user")
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Style.margins
|
|
Layout.rightMargin: Style.margins
|
|
enabled: usernameTextField.displayText.length >= 3 && passwordTextField.isValid
|
|
onClicked: {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|