diff --git a/src/data/auth.ts b/src/data/auth.ts index 29095dcf13..8b68f4c788 100644 --- a/src/data/auth.ts +++ b/src/data/auth.ts @@ -138,6 +138,17 @@ export const adminChangePassword = ( password, }); +export const adminChangeUsername = ( + hass: HomeAssistant, + userId: string, + username: string +) => + hass.callWS({ + type: "config/auth_provider/homeassistant/admin_change_username", + user_id: userId, + username, + }); + export const deleteAllRefreshTokens = ( hass: HomeAssistant, token_type?: RefreshTokenType, diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index 92ee079d7d..c14deab0c2 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -8,6 +8,7 @@ import "../../../components/ha-formfield"; import "../../../components/ha-picture-upload"; import type { HaPictureUpload } from "../../../components/ha-picture-upload"; import "../../../components/ha-textfield"; +import { adminChangeUsername } from "../../../data/auth"; import { PersonMutableParams } from "../../../data/person"; import { deleteUser, @@ -19,10 +20,11 @@ import { import { showAlertDialog, showConfirmationDialog, + showPromptDialog, } from "../../../dialogs/generic/show-dialog-box"; import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog"; -import { ValueChangedEvent, HomeAssistant } from "../../../types"; import { haStyleDialog } from "../../../resources/styles"; +import { HomeAssistant, ValueChangedEvent } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showAddUserDialog } from "../users/show-dialog-add-user"; import { showAdminChangePasswordDialog } from "../users/show-dialog-admin-change-password"; @@ -136,9 +138,9 @@ class DialogPersonDetail extends LitElement { > ${this._user && this.hass.user?.is_owner ? html` - ${this.hass.localize( - "ui.panel.config.users.editor.change_password" - )} - ` + slot="secondaryAction" + @click=${this._changeUsername} + > + ${this.hass.localize( + "ui.panel.config.users.editor.change_username" + )} + + + ${this.hass.localize( + "ui.panel.config.users.editor.change_password" + )} + ` : ""} ` : nothing} @@ -292,11 +302,14 @@ class DialogPersonDetail extends LitElement { userAddedCallback: async (user?: User) => { if (user) { target.checked = true; + if (this._params!.entry) { + await this._params!.updateEntry({ user_id: user.id }); + } + this._params?.refreshUsers(); this._user = user; this._userId = user.id; this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN); this._localOnly = user.local_only; - this._params?.refreshUsers(); } }, name: this._name, @@ -320,6 +333,9 @@ class DialogPersonDetail extends LitElement { await deleteUser(this.hass, this._userId); this._params?.refreshUsers(); this._userId = undefined; + this._user = undefined; + this._isAdmin = undefined; + this._localOnly = undefined; } } @@ -349,6 +365,53 @@ class DialogPersonDetail extends LitElement { showAdminChangePasswordDialog(this, { userId: this._user.id }); } + private async _changeUsername() { + 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 newUsername = await showPromptDialog(this, { + inputLabel: this.hass.localize( + "ui.panel.config.users.change_username.new_username" + ), + confirmText: this.hass.localize( + "ui.panel.config.users.change_username.change" + ), + title: this.hass.localize( + "ui.panel.config.users.change_username.caption" + ), + defaultValue: this._user.username!, + }); + if (newUsername) { + try { + await adminChangeUsername(this.hass, this._user.id, newUsername); + this._params?.refreshUsers(); + this._user = { ...this._user, username: newUsername }; + showAlertDialog(this, { + text: this.hass.localize( + "ui.panel.config.users.change_username.username_changed" + ), + }); + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.users.change_username.failed" + ), + text: err.message, + }); + } + } + } + private async _updateEntry() { this._submitting = true; try { diff --git a/src/panels/config/users/dialog-user-detail.ts b/src/panels/config/users/dialog-user-detail.ts index 18e20cb107..2b96c4aa67 100644 --- a/src/panels/config/users/dialog-user-detail.ts +++ b/src/panels/config/users/dialog-user-detail.ts @@ -10,12 +10,16 @@ import "../../../components/ha-label"; import "../../../components/ha-svg-icon"; import "../../../components/ha-switch"; import "../../../components/ha-textfield"; +import { adminChangeUsername } from "../../../data/auth"; import { computeUserBadges, SYSTEM_GROUP_ID_ADMIN, SYSTEM_GROUP_ID_USER, } from "../../../data/user"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { + showAlertDialog, + showPromptDialog, +} from "../../../dialogs/generic/show-dialog-box"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { showAdminChangePasswordDialog } from "./show-dialog-admin-change-password"; @@ -172,11 +176,15 @@ class DialogUserDetail extends LitElement { ` : ""} ${!user.system_generated && this.hass.user?.is_owner - ? html` - ${this.hass.localize( - "ui.panel.config.users.editor.change_password" - )} - ` + ? html` + ${this.hass.localize( + "ui.panel.config.users.editor.change_username" + )} + ${this.hass.localize( + "ui.panel.config.users.editor.change_password" + )} + ` : ""} @@ -250,6 +258,56 @@ class DialogUserDetail extends LitElement { } } + private async _changeUsername() { + const credential = this._params?.entry.credentials.find( + (cred) => cred.type === "homeassistant" + ); + if (!credential) { + showAlertDialog(this, { + title: "No Home Assistant credentials found.", + }); + return; + } + const newUsername = await showPromptDialog(this, { + inputLabel: this.hass.localize( + "ui.panel.config.users.change_username.new_username" + ), + confirmText: this.hass.localize( + "ui.panel.config.users.change_username.change" + ), + title: this.hass.localize( + "ui.panel.config.users.change_username.caption" + ), + defaultValue: this._params!.entry.username!, + }); + if (newUsername) { + try { + await adminChangeUsername( + this.hass, + this._params!.entry.id, + newUsername + ); + this._params = { + ...this._params!, + entry: { ...this._params!.entry, username: newUsername }, + }; + this._params.replaceEntry(this._params.entry); + showAlertDialog(this, { + text: this.hass.localize( + "ui.panel.config.users.change_username.username_changed" + ), + }); + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.users.change_username.failed" + ), + text: err.message, + }); + } + } + } + private async _changePassword() { const credential = this._params?.entry.credentials.find( (cred) => cred.type === "homeassistant" diff --git a/src/panels/config/users/ha-config-users.ts b/src/panels/config/users/ha-config-users.ts index e761db2fa8..89bdea4bf7 100644 --- a/src/panels/config/users/ha-config-users.ts +++ b/src/panels/config/users/ha-config-users.ts @@ -237,6 +237,11 @@ export class HaConfigUsers extends LitElement { showUserDetailDialog(this, { entry, + replaceEntry: (newEntry: User) => { + this._users = this._users!.map((ent) => + ent.id === newEntry.id ? newEntry : ent + ); + }, updateEntry: async (values) => { const updated = await updateUser(this.hass!, entry!.id, values); this._users = this._users!.map((ent) => diff --git a/src/panels/config/users/show-dialog-user-detail.ts b/src/panels/config/users/show-dialog-user-detail.ts index 5dd5372075..e9d05cb112 100644 --- a/src/panels/config/users/show-dialog-user-detail.ts +++ b/src/panels/config/users/show-dialog-user-detail.ts @@ -4,6 +4,7 @@ import { UpdateUserParams, User } from "../../../data/user"; export interface UserDetailDialogParams { entry: User; updateEntry: (updates: Partial) => Promise; + replaceEntry: (entry: User) => void; removeEntry: () => Promise; } diff --git a/src/translations/en.json b/src/translations/en.json index e5a7b264ce..ed0696c663 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4382,6 +4382,7 @@ "name": "Display name", "username": "Username", "change_password": "Change password", + "change_username": "Change username", "activate_user": "Activate user", "deactivate_user": "Deactivate user", "delete_user": "Delete user", @@ -4415,6 +4416,13 @@ "change": "Change", "password_no_match": "Passwords don't match", "password_changed": "The password has been changed successfully." + }, + "change_username": { + "caption": "Change username", + "new_username": "New username", + "change": "Change", + "username_changed": "The username has been changed successfully.", + "failed": "Failed to change username" } }, "application_credentials": {