Integrate user management in person (#7170)

This commit is contained in:
Bram Kragten 2020-09-30 15:22:40 +02:00 committed by GitHub
parent 11e3503dc2
commit d43b5d3337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 218 additions and 31 deletions

View File

@ -136,6 +136,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
translationKey: "ui.panel.config.users.caption",
iconPath: mdiBadgeAccountHorizontal,
core: true,
advancedOnly: true,
},
],
general: [

View File

@ -22,6 +22,22 @@ import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
import "../../../components/ha-formfield";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import {
User,
SYSTEM_GROUP_ID_ADMIN,
deleteUser,
SYSTEM_GROUP_ID_USER,
updateUser,
} from "../../../data/user";
import {
showAlertDialog,
showPromptDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { adminChangePassword } from "../../../data/auth";
import { showAddUserDialog } from "../users/show-dialog-add-user";
const includeDomains = ["device_tracker"];
@ -39,6 +55,10 @@ class DialogPersonDetail extends LitElement {
@internalProperty() private _userId?: string;
@internalProperty() private _user?: User;
@internalProperty() private _isAdmin?: boolean;
@internalProperty() private _deviceTrackers!: string[];
@internalProperty() private _picture!: string | null;
@ -64,9 +84,15 @@ class DialogPersonDetail extends LitElement {
this._userId = this._params.entry.user_id || undefined;
this._deviceTrackers = this._params.entry.device_trackers || [];
this._picture = this._params.entry.picture || null;
this._user = this._userId
? this._params.users.find((user) => user.id === this._userId)
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
} else {
this._name = "";
this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
this._deviceTrackers = [];
this._picture = null;
}
@ -115,15 +141,37 @@ class DialogPersonDetail extends LitElement {
@change=${this._pictureChanged}
></ha-picture-upload>
<ha-user-picker
label="${this.hass!.localize(
"ui.panel.config.person.detail.linked_user"
)}"
.hass=${this.hass}
.value=${this._userId}
.users=${this._params.users}
@value-changed=${this._userChanged}
></ha-user-picker>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.person.detail.allow_login"
)}
>
<ha-switch
@change=${this._allowLoginChanged}
.disabled=${this._user &&
(this._user.id === this.hass.user?.id ||
this._user.system_generated ||
this._user.is_owner)}
.checked=${this._userId}
></ha-switch>
</ha-formfield>
${this._user
? html`<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.person.detail.admin"
)}
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${this._user.system_generated ||
this._user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>
</ha-switch>
</ha-formfield>`
: ""}
${this._deviceTrackersAvailable(this.hass)
? html`
<p>
@ -185,10 +233,21 @@ class DialogPersonDetail extends LitElement {
slot="secondaryAction"
class="warning"
@click="${this._deleteEntry}"
.disabled=${this._submitting}
.disabled=${(this._user && this._user.is_owner) ||
this._submitting}
>
${this.hass!.localize("ui.panel.config.person.detail.delete")}
</mwc-button>
${this._user && this.hass.user?.is_owner
? html`<mwc-button
slot="secondaryAction"
@click=${this._changePassword}
>
${this.hass.localize(
"ui.panel.config.users.editor.change_password"
)}
</mwc-button>`
: ""}
`
: html``}
<mwc-button
@ -213,9 +272,47 @@ class DialogPersonDetail extends LitElement {
this._name = ev.detail.value;
}
private _userChanged(ev: PolymerChangedEvent<string>) {
this._error = undefined;
this._userId = ev.detail.value;
private async _adminChanged(ev): Promise<void> {
this._isAdmin = ev.target.checked;
}
private async _allowLoginChanged(ev): Promise<void> {
const target = ev.target;
if (target.checked) {
target.checked = false;
showAddUserDialog(this, {
userAddedCallback: async (user?: User) => {
if (user) {
target.checked = true;
this._user = user;
this._userId = user.id;
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._params?.refreshUsers();
}
},
name: this._name,
});
} else if (this._userId) {
if (
!(await showConfirmationDialog(this, {
text: this.hass!.localize(
"ui.panel.config.person.detail.confirm_delete_user",
"name",
this._name
),
confirmText: this.hass!.localize(
"ui.panel.config.person.detail.delete"
),
dismissText: this.hass!.localize("ui.common.cancel"),
}))
) {
target.checked = true;
return;
}
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
}
}
private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) {
@ -228,9 +325,70 @@ class DialogPersonDetail extends LitElement {
this._picture = (ev.target as HaPictureUpload).value;
}
private async _changePassword() {
if (!this._user) {
return;
}
const credential = this._user.credentials.find(
(cred) => cred.type === "homeassistant"
);
if (!credential) {
showAlertDialog(this, {
title: "No Home Assistant credentials found.",
});
return;
}
const newPassword = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
inputType: "password",
inputLabel: this.hass.localize(
"ui.panel.config.users.editor.new_password"
),
});
if (!newPassword) {
return;
}
const confirmPassword = await showPromptDialog(this, {
title: this.hass.localize("ui.panel.config.users.editor.change_password"),
inputType: "password",
inputLabel: this.hass.localize(
"ui.panel.config.users.add_user.password_confirm"
),
});
if (!confirmPassword) {
return;
}
if (newPassword !== confirmPassword) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.add_user.password_not_match"
),
});
return;
}
await adminChangePassword(this.hass, this._user.id, newPassword);
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.users.editor.password_changed"
),
});
}
private async _updateEntry() {
this._submitting = true;
try {
if (
(this._userId && this._name !== this._params!.entry?.name) ||
this._isAdmin !== this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN)
) {
await updateUser(this.hass!, this._userId!, {
name: this._name.trim(),
group_ids: [
this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER,
],
});
this._params?.refreshUsers();
}
const values: PersonMutableParams = {
name: this._name.trim(),
device_trackers: this._deviceTrackers,
@ -254,6 +412,9 @@ class DialogPersonDetail extends LitElement {
this._submitting = true;
try {
if (await this._params!.removeEntry()) {
if (this._params!.entry!.user_id) {
deleteUser(this.hass, this._params!.entry!.user_id);
}
this._params = undefined;
}
} finally {
@ -275,6 +436,10 @@ class DialogPersonDetail extends LitElement {
ha-picture-upload {
display: block;
}
ha-formfield {
display: block;
padding: 16px 0;
}
ha-user-picker {
margin-top: 16px;
}

View File

@ -1,6 +1,7 @@
import { mdiPlus } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@material/mwc-fab";
import {
css,
CSSResult,
@ -223,6 +224,9 @@ class HaConfigPerson extends LitElement {
return false;
}
},
refreshUsers: () => {
this._usersLoad = fetchUsers(this.hass!);
},
});
}

View File

@ -5,6 +5,7 @@ import { User } from "../../../data/user";
export interface PersonDetailDialogParams {
entry?: Person;
users: User[];
refreshUsers: () => void;
createEntry: (values: PersonMutableParams) => Promise<unknown>;
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry: () => Promise<boolean>;

View File

@ -50,15 +50,24 @@ export class DialogAddUser extends LitElement {
@internalProperty() private _isAdmin?: boolean;
@internalProperty() private _allowChangeName = true;
public showDialog(params: AddUserDialogParams) {
this._params = params;
this._name = "";
this._name = this._params.name || "";
this._username = "";
this._password = "";
this._passwordConfirm = "";
this._isAdmin = false;
this._error = undefined;
this._loading = false;
if (this._params.name) {
this._allowChangeName = false;
this._maybePopulateUsername();
} else {
this._allowChangeName = true;
}
}
protected firstUpdated(changedProperties: PropertyValues) {
@ -84,19 +93,22 @@ export class DialogAddUser extends LitElement {
>
<div>
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
<paper-input
class="name"
name="name"
.label=${this.hass.localize("ui.panel.config.users.add_user.name")}
.value=${this._name}
required
auto-validate
autocapitalize="on"
.errorMessage=${this.hass.localize("ui.common.error_required")}
@value-changed=${this._handleValueChanged}
@blur=${this._maybePopulateUsername}
></paper-input>
${this._allowChangeName
? html` <paper-input
class="name"
name="name"
.label=${this.hass.localize(
"ui.panel.config.users.add_user.name"
)}
.value=${this._name}
required
auto-validate
autocapitalize="on"
.errorMessage=${this.hass.localize("ui.common.error_required")}
@value-changed=${this._handleValueChanged}
@blur=${this._maybePopulateUsername}
></paper-input>`
: ""}
<paper-input
class="username"
name="username"

View File

@ -47,7 +47,7 @@ class DialogUserDetail extends LitElement {
this._params = params;
this._error = undefined;
this._name = params.entry.name || "";
this._isAdmin = params.entry.group_ids[0] === SYSTEM_GROUP_ID_ADMIN;
this._isAdmin = params.entry.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
await this.updateComplete;
}
@ -112,7 +112,7 @@ class DialogUserDetail extends LitElement {
.dir=${computeRTLDirection(this.hass)}
>
<ha-switch
.disabled=${user.system_generated}
.disabled=${user.system_generated || user.is_owner}
.checked=${this._isAdmin}
@change=${this._adminChanged}
>

View File

@ -3,6 +3,7 @@ import { User } from "../../../data/user";
export interface AddUserDialogParams {
userAddedCallback: (user: User) => void;
name?: string;
}
export const loadAddUserDialog = () =>

View File

@ -1656,7 +1656,10 @@
"device_tracker_pick": "Pick device to track",
"delete": "Delete",
"create": "Create",
"update": "Update"
"update": "Update",
"confirm_delete_user": "Are you sure you want to delete the user account for {name}? You can still track the user, but the person will no longer be able to login.",
"admin": "[%key:ui::panel::config::users::editor::admin%]",
"allow_login": "Allow person to login"
}
},
"zone": {
@ -1676,7 +1679,7 @@
"new_zone": "New Zone",
"name": "Name",
"icon": "Icon",
"icon_error_msg": "Icon should be in the format prefix:iconname, for example: mdi:home",
"icon_error_msg": "Icon should be in the format ''prefix:iconname'', for example: ''mdi:home''",
"radius": "Radius",
"latitude": "Latitude",
"longitude": "Longitude",