Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/panels/config/ha-panel-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
translationKey: "ui.panel.config.users.caption",
iconPath: mdiBadgeAccountHorizontal,
core: true,
advancedOnly: true,
},
],
general: [
Expand Down
179 changes: 166 additions & 13 deletions src/panels/config/person/dialog-person-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];

Expand All @@ -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;
Expand All @@ -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[0] === SYSTEM_GROUP_ID_ADMIN;
Comment thread
bramkragten marked this conversation as resolved.
Outdated
} else {
this._name = "";
this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
this._deviceTrackers = [];
this._picture = null;
}
Expand Down Expand Up @@ -115,15 +141,33 @@ 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="Allow person to login">
<ha-switch
@change=${this._allowLoginChanged}
.disabled=${this._user &&
Comment thread
ludeeus marked this conversation as resolved.
(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.users.editor.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>
Expand Down Expand Up @@ -185,10 +229,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
Expand All @@ -213,9 +268,39 @@ 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[0] === SYSTEM_GROUP_ID_ADMIN;
Comment thread
bramkragten marked this conversation as resolved.
Outdated
this._params?.refreshUsers();
}
},
name: this._name,
});
} else if (this._userId) {
if (
!(await showConfirmationDialog(this, {
text: "Are you sure you want to remove this user?",
Comment thread
bramkragten marked this conversation as resolved.
Outdated
}))
) {
target.checked = true;
return;
}
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers();
this._userId = undefined;
}
}

private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) {
Expand All @@ -228,9 +313,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.",
Comment thread
ludeeus marked this conversation as resolved.
Comment thread
ludeeus marked this conversation as resolved.
});
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[0] === SYSTEM_GROUP_ID_ADMIN)
Comment thread
bramkragten marked this conversation as resolved.
Outdated
) {
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,
Expand All @@ -254,6 +400,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 {
Expand All @@ -275,6 +424,10 @@ class DialogPersonDetail extends LitElement {
ha-picture-upload {
display: block;
}
ha-formfield {
display: block;
padding: 16px 0;
}
ha-user-picker {
margin-top: 16px;
}
Expand Down
6 changes: 5 additions & 1 deletion src/panels/config/person/ha-config-person.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -21,7 +22,7 @@ import {
Person,
updatePerson,
} from "../../../data/person";
import { fetchUsers, User } from "../../../data/user";
import { fetchUsers, User, updateUser } from "../../../data/user";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
Expand Down Expand Up @@ -223,6 +224,9 @@ class HaConfigPerson extends LitElement {
return false;
}
},
refreshUsers: () => {
this._usersLoad = fetchUsers(this.hass!);
},
});
}

Expand Down
1 change: 1 addition & 0 deletions src/panels/config/person/show-dialog-person-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
40 changes: 26 additions & 14 deletions src/panels/config/users/dialog-add-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/panels/config/users/dialog-user-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
>
Expand Down
1 change: 1 addition & 0 deletions src/panels/config/users/show-dialog-add-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { User } from "../../../data/user";

export interface AddUserDialogParams {
userAddedCallback: (user: User) => void;
name?: string;
}

export const loadAddUserDialog = () =>
Expand Down