mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +00:00
Integrate user management in person (#7170)
This commit is contained in:
parent
11e3503dc2
commit
d43b5d3337
@ -136,6 +136,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
translationKey: "ui.panel.config.users.caption",
|
translationKey: "ui.panel.config.users.caption",
|
||||||
iconPath: mdiBadgeAccountHorizontal,
|
iconPath: mdiBadgeAccountHorizontal,
|
||||||
core: true,
|
core: true,
|
||||||
|
advancedOnly: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
general: [
|
general: [
|
||||||
|
@ -22,6 +22,22 @@ import { haStyleDialog } from "../../../resources/styles";
|
|||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { documentationUrl } from "../../../util/documentation-url";
|
import { documentationUrl } from "../../../util/documentation-url";
|
||||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
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"];
|
const includeDomains = ["device_tracker"];
|
||||||
|
|
||||||
@ -39,6 +55,10 @@ class DialogPersonDetail extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _userId?: string;
|
@internalProperty() private _userId?: string;
|
||||||
|
|
||||||
|
@internalProperty() private _user?: User;
|
||||||
|
|
||||||
|
@internalProperty() private _isAdmin?: boolean;
|
||||||
|
|
||||||
@internalProperty() private _deviceTrackers!: string[];
|
@internalProperty() private _deviceTrackers!: string[];
|
||||||
|
|
||||||
@internalProperty() private _picture!: string | null;
|
@internalProperty() private _picture!: string | null;
|
||||||
@ -64,9 +84,15 @@ class DialogPersonDetail extends LitElement {
|
|||||||
this._userId = this._params.entry.user_id || undefined;
|
this._userId = this._params.entry.user_id || undefined;
|
||||||
this._deviceTrackers = this._params.entry.device_trackers || [];
|
this._deviceTrackers = this._params.entry.device_trackers || [];
|
||||||
this._picture = this._params.entry.picture || null;
|
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 {
|
} else {
|
||||||
this._name = "";
|
this._name = "";
|
||||||
this._userId = undefined;
|
this._userId = undefined;
|
||||||
|
this._user = undefined;
|
||||||
|
this._isAdmin = undefined;
|
||||||
this._deviceTrackers = [];
|
this._deviceTrackers = [];
|
||||||
this._picture = null;
|
this._picture = null;
|
||||||
}
|
}
|
||||||
@ -115,15 +141,37 @@ class DialogPersonDetail extends LitElement {
|
|||||||
@change=${this._pictureChanged}
|
@change=${this._pictureChanged}
|
||||||
></ha-picture-upload>
|
></ha-picture-upload>
|
||||||
|
|
||||||
<ha-user-picker
|
<ha-formfield
|
||||||
label="${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
"ui.panel.config.person.detail.linked_user"
|
"ui.panel.config.person.detail.allow_login"
|
||||||
)}"
|
)}
|
||||||
.hass=${this.hass}
|
>
|
||||||
.value=${this._userId}
|
<ha-switch
|
||||||
.users=${this._params.users}
|
@change=${this._allowLoginChanged}
|
||||||
@value-changed=${this._userChanged}
|
.disabled=${this._user &&
|
||||||
></ha-user-picker>
|
(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)
|
${this._deviceTrackersAvailable(this.hass)
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p>
|
||||||
@ -185,10 +233,21 @@ class DialogPersonDetail extends LitElement {
|
|||||||
slot="secondaryAction"
|
slot="secondaryAction"
|
||||||
class="warning"
|
class="warning"
|
||||||
@click="${this._deleteEntry}"
|
@click="${this._deleteEntry}"
|
||||||
.disabled=${this._submitting}
|
.disabled=${(this._user && this._user.is_owner) ||
|
||||||
|
this._submitting}
|
||||||
>
|
>
|
||||||
${this.hass!.localize("ui.panel.config.person.detail.delete")}
|
${this.hass!.localize("ui.panel.config.person.detail.delete")}
|
||||||
</mwc-button>
|
</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``}
|
: html``}
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@ -213,9 +272,47 @@ class DialogPersonDetail extends LitElement {
|
|||||||
this._name = ev.detail.value;
|
this._name = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _userChanged(ev: PolymerChangedEvent<string>) {
|
private async _adminChanged(ev): Promise<void> {
|
||||||
this._error = undefined;
|
this._isAdmin = ev.target.checked;
|
||||||
this._userId = ev.detail.value;
|
}
|
||||||
|
|
||||||
|
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[]>) {
|
private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) {
|
||||||
@ -228,9 +325,70 @@ class DialogPersonDetail extends LitElement {
|
|||||||
this._picture = (ev.target as HaPictureUpload).value;
|
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() {
|
private async _updateEntry() {
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
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 = {
|
const values: PersonMutableParams = {
|
||||||
name: this._name.trim(),
|
name: this._name.trim(),
|
||||||
device_trackers: this._deviceTrackers,
|
device_trackers: this._deviceTrackers,
|
||||||
@ -254,6 +412,9 @@ class DialogPersonDetail extends LitElement {
|
|||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
if (await this._params!.removeEntry()) {
|
if (await this._params!.removeEntry()) {
|
||||||
|
if (this._params!.entry!.user_id) {
|
||||||
|
deleteUser(this.hass, this._params!.entry!.user_id);
|
||||||
|
}
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -275,6 +436,10 @@ class DialogPersonDetail extends LitElement {
|
|||||||
ha-picture-upload {
|
ha-picture-upload {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
ha-formfield {
|
||||||
|
display: block;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
ha-user-picker {
|
ha-user-picker {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { mdiPlus } from "@mdi/js";
|
import { mdiPlus } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import "@material/mwc-fab";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
@ -223,6 +224,9 @@ class HaConfigPerson extends LitElement {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
refreshUsers: () => {
|
||||||
|
this._usersLoad = fetchUsers(this.hass!);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { User } from "../../../data/user";
|
|||||||
export interface PersonDetailDialogParams {
|
export interface PersonDetailDialogParams {
|
||||||
entry?: Person;
|
entry?: Person;
|
||||||
users: User[];
|
users: User[];
|
||||||
|
refreshUsers: () => void;
|
||||||
createEntry: (values: PersonMutableParams) => Promise<unknown>;
|
createEntry: (values: PersonMutableParams) => Promise<unknown>;
|
||||||
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
|
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
|
||||||
removeEntry: () => Promise<boolean>;
|
removeEntry: () => Promise<boolean>;
|
||||||
|
@ -50,15 +50,24 @@ export class DialogAddUser extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _isAdmin?: boolean;
|
@internalProperty() private _isAdmin?: boolean;
|
||||||
|
|
||||||
|
@internalProperty() private _allowChangeName = true;
|
||||||
|
|
||||||
public showDialog(params: AddUserDialogParams) {
|
public showDialog(params: AddUserDialogParams) {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._name = "";
|
this._name = this._params.name || "";
|
||||||
this._username = "";
|
this._username = "";
|
||||||
this._password = "";
|
this._password = "";
|
||||||
this._passwordConfirm = "";
|
this._passwordConfirm = "";
|
||||||
this._isAdmin = false;
|
this._isAdmin = false;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._loading = false;
|
this._loading = false;
|
||||||
|
|
||||||
|
if (this._params.name) {
|
||||||
|
this._allowChangeName = false;
|
||||||
|
this._maybePopulateUsername();
|
||||||
|
} else {
|
||||||
|
this._allowChangeName = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProperties: PropertyValues) {
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
@ -84,19 +93,22 @@ export class DialogAddUser extends LitElement {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
<paper-input
|
${this._allowChangeName
|
||||||
class="name"
|
? html` <paper-input
|
||||||
name="name"
|
class="name"
|
||||||
.label=${this.hass.localize("ui.panel.config.users.add_user.name")}
|
name="name"
|
||||||
.value=${this._name}
|
.label=${this.hass.localize(
|
||||||
required
|
"ui.panel.config.users.add_user.name"
|
||||||
auto-validate
|
)}
|
||||||
autocapitalize="on"
|
.value=${this._name}
|
||||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
required
|
||||||
@value-changed=${this._handleValueChanged}
|
auto-validate
|
||||||
@blur=${this._maybePopulateUsername}
|
autocapitalize="on"
|
||||||
></paper-input>
|
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||||
|
@value-changed=${this._handleValueChanged}
|
||||||
|
@blur=${this._maybePopulateUsername}
|
||||||
|
></paper-input>`
|
||||||
|
: ""}
|
||||||
<paper-input
|
<paper-input
|
||||||
class="username"
|
class="username"
|
||||||
name="username"
|
name="username"
|
||||||
|
@ -47,7 +47,7 @@ class DialogUserDetail extends LitElement {
|
|||||||
this._params = params;
|
this._params = params;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._name = params.entry.name || "";
|
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;
|
await this.updateComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class DialogUserDetail extends LitElement {
|
|||||||
.dir=${computeRTLDirection(this.hass)}
|
.dir=${computeRTLDirection(this.hass)}
|
||||||
>
|
>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.disabled=${user.system_generated}
|
.disabled=${user.system_generated || user.is_owner}
|
||||||
.checked=${this._isAdmin}
|
.checked=${this._isAdmin}
|
||||||
@change=${this._adminChanged}
|
@change=${this._adminChanged}
|
||||||
>
|
>
|
||||||
|
@ -3,6 +3,7 @@ import { User } from "../../../data/user";
|
|||||||
|
|
||||||
export interface AddUserDialogParams {
|
export interface AddUserDialogParams {
|
||||||
userAddedCallback: (user: User) => void;
|
userAddedCallback: (user: User) => void;
|
||||||
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadAddUserDialog = () =>
|
export const loadAddUserDialog = () =>
|
||||||
|
@ -1656,7 +1656,10 @@
|
|||||||
"device_tracker_pick": "Pick device to track",
|
"device_tracker_pick": "Pick device to track",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"create": "Create",
|
"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": {
|
"zone": {
|
||||||
@ -1676,7 +1679,7 @@
|
|||||||
"new_zone": "New Zone",
|
"new_zone": "New Zone",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"icon": "Icon",
|
"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",
|
"radius": "Radius",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude",
|
"longitude": "Longitude",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user