From 20cc9c9b42dfe649640a699edce116a068a20988 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 30 Mar 2020 20:52:53 +0200 Subject: [PATCH] Update user config pages (#5354) * Update user config pages * remove user-editor * Update * Update dialog-add-user.ts --- src/data/auth.ts | 13 + src/data/user.ts | 12 +- src/panels/config/users/dialog-add-user.ts | 242 +++++++++++++++++ src/panels/config/users/dialog-user-detail.ts | 209 ++++++++++++++ .../config/users/ha-config-user-picker.js | 166 ----------- src/panels/config/users/ha-config-users.js | 109 -------- src/panels/config/users/ha-config-users.ts | 192 +++++++++++++ src/panels/config/users/ha-dialog-add-user.js | 201 -------------- src/panels/config/users/ha-user-editor.ts | 257 ------------------ .../config/users/show-dialog-add-user.ts | 20 ++ .../config/users/show-dialog-user-detail.ts | 22 ++ src/translations/en.json | 10 +- 12 files changed, 712 insertions(+), 741 deletions(-) create mode 100644 src/panels/config/users/dialog-add-user.ts create mode 100644 src/panels/config/users/dialog-user-detail.ts delete mode 100644 src/panels/config/users/ha-config-user-picker.js delete mode 100644 src/panels/config/users/ha-config-users.js create mode 100644 src/panels/config/users/ha-config-users.ts delete mode 100644 src/panels/config/users/ha-dialog-add-user.js delete mode 100644 src/panels/config/users/ha-user-editor.ts create mode 100644 src/panels/config/users/show-dialog-add-user.ts create mode 100644 src/panels/config/users/show-dialog-user-detail.ts diff --git a/src/data/auth.ts b/src/data/auth.ts index c768b95f41..6437d31b62 100644 --- a/src/data/auth.ts +++ b/src/data/auth.ts @@ -25,3 +25,16 @@ export const fetchAuthProviders = () => fetch("/auth/providers", { credentials: "same-origin", }); + +export const createAuthForUser = async ( + hass: HomeAssistant, + userId: string, + username: string, + password: string +) => + hass.callWS({ + type: "config/auth_provider/homeassistant/create", + user_id: userId, + username, + password, + }); diff --git a/src/data/user.ts b/src/data/user.ts index 41045906c4..905b138635 100644 --- a/src/data/user.ts +++ b/src/data/user.ts @@ -5,6 +5,8 @@ export const SYSTEM_GROUP_ID_ADMIN = "system-admin"; export const SYSTEM_GROUP_ID_USER = "system-users"; export const SYSTEM_GROUP_ID_READ_ONLY = "system-read-only"; +export const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN]; + export interface User { id: string; name: string; @@ -15,7 +17,7 @@ export interface User { credentials: Credential[]; } -interface UpdateUserParams { +export interface UpdateUserParams { name?: User["name"]; group_ids?: User["group_ids"]; } @@ -25,10 +27,16 @@ export const fetchUsers = async (hass: HomeAssistant) => type: "config/auth/list", }); -export const createUser = async (hass: HomeAssistant, name: string) => +export const createUser = async ( + hass: HomeAssistant, + name: string, + // tslint:disable-next-line: variable-name + group_ids?: User["group_ids"] +) => hass.callWS<{ user: User }>({ type: "config/auth/create", name, + group_ids, }); export const updateUser = async ( diff --git a/src/panels/config/users/dialog-add-user.ts b/src/panels/config/users/dialog-add-user.ts new file mode 100644 index 0000000000..e30d00eca3 --- /dev/null +++ b/src/panels/config/users/dialog-add-user.ts @@ -0,0 +1,242 @@ +import "@material/mwc-button"; +import "@polymer/paper-spinner/paper-spinner"; + +import "../../../components/ha-dialog"; +import "../../../resources/ha-style"; +import { + LitElement, + html, + TemplateResult, + customElement, + property, + PropertyValues, +} from "lit-element"; +import { HomeAssistant } from "../../../types"; +import { PolymerChangedEvent } from "../../../polymer-types"; +import { AddUserDialogParams } from "./show-dialog-add-user"; +import { + User, + SYSTEM_GROUP_ID_USER, + GROUPS, + createUser, + deleteUser, +} from "../../../data/user"; +import { createAuthForUser } from "../../../data/auth"; + +@customElement("dialog-add-user") +export class DialogAddUser extends LitElement { + @property() public hass!: HomeAssistant; + @property() private _loading = false; + // Error message when can't talk to server etc + @property() private _error?: string; + @property() private _params?: AddUserDialogParams; + @property() private _name?: string; + @property() private _username?: string; + @property() private _password?: string; + @property() private _group?: string; + + public showDialog(params: AddUserDialogParams) { + this._params = params; + this._name = ""; + this._username = ""; + this._password = ""; + this._group = SYSTEM_GROUP_ID_USER; + this._error = undefined; + this._loading = false; + } + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + this.addEventListener("keypress", (ev) => { + if (ev.keyCode === 13) { + this._createUser(ev); + } + }); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + return html` + +
+ ${this._error + ? html` +
${this._error}
+ ` + : ""} + + + + + + ${GROUPS.map( + (groupId) => html` + + ${this.hass.localize(`groups.${groupId}`)} + + ` + )} + + + ${this._group === SYSTEM_GROUP_ID_USER + ? html` +
+ The users group is a work in progress. The user will be unable + to administer the instance via the UI. We're still auditing all + management API endpoints to ensure that they correctly limit + access to administrators. + ` + : ""} +
+ + ${this.hass!.localize("ui.common.cancel")} + + ${this._loading + ? html` +
+ +
+ ` + : html` + + ${this.hass.localize("ui.panel.config.users.add_user.create")} + + `} +
+ `; + } + + private _close() { + this._params = undefined; + } + + private _maybePopulateUsername() { + if (this._username || !this._name) { + return; + } + + const parts = this._name.split(" "); + + if (parts.length) { + this._username = parts[0].toLowerCase(); + } + } + + private _nameChanged(ev: PolymerChangedEvent) { + this._error = undefined; + this._name = ev.detail.value; + } + + private _usernameChanged(ev: PolymerChangedEvent) { + this._error = undefined; + this._username = ev.detail.value; + } + + private _passwordChanged(ev: PolymerChangedEvent) { + this._error = undefined; + this._password = ev.detail.value; + } + + private async _handleGroupChange(ev): Promise { + this._group = ev.detail.item.getAttribute("group-id"); + } + + private async _createUser(ev) { + ev.preventDefault(); + if (!this._name || !this._username || !this._password) { + return; + } + + this._loading = true; + this._error = ""; + + let user: User; + try { + const userResponse = await createUser(this.hass, this._name, [ + this._group!, + ]); + user = userResponse.user; + } catch (err) { + this._loading = false; + this._error = err.code; + return; + } + + try { + await createAuthForUser( + this.hass, + user.id, + this._username, + this._password + ); + } catch (err) { + await deleteUser(this.hass, user.id); + this._loading = false; + this._error = err.code; + return; + } + + this._params!.userAddedCallback(user); + this._close(); + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-add-user": DialogAddUser; + } +} diff --git a/src/panels/config/users/dialog-user-detail.ts b/src/panels/config/users/dialog-user-detail.ts new file mode 100644 index 0000000000..b9ef3de155 --- /dev/null +++ b/src/panels/config/users/dialog-user-detail.ts @@ -0,0 +1,209 @@ +import "@material/mwc-button"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-tooltip/paper-tooltip"; +import { + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, + css, +} from "lit-element"; +import "../../../components/entity/ha-entities-picker"; +import "../../../components/user/ha-user-picker"; +import { PolymerChangedEvent } from "../../../polymer-types"; +import { haStyleDialog } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; +import { UserDetailDialogParams } from "./show-dialog-user-detail"; +import { createCloseHeading } from "../../../components/ha-dialog"; +import { GROUPS, SYSTEM_GROUP_ID_USER } from "../../../data/user"; + +@customElement("dialog-user-detail") +class DialogUserDetail extends LitElement { + @property() public hass!: HomeAssistant; + @property() private _name!: string; + @property() private _group?: string; + @property() private _error?: string; + @property() private _params?: UserDetailDialogParams; + @property() private _submitting: boolean = false; + + public async showDialog(params: UserDetailDialogParams): Promise { + this._params = params; + this._error = undefined; + this._name = params.entry.name || ""; + this._group = params.entry.group_ids[0]; + await this.updateComplete; + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + const user = this._params.entry; + return html` + +
+ ${this._error + ? html` +
${this._error}
+ ` + : ""} +
+ + + + ${GROUPS.map( + (groupId) => html` + + ${this.hass.localize(`groups.${groupId}`)} + + ` + )} + + + ${this._group === SYSTEM_GROUP_ID_USER + ? html` +
+ The users group is a work in progress. The user will be unable + to administer the instance via the UI. We're still auditing + all management API endpoints to ensure that they correctly + limit access to administrators. + ` + : ""} + + + + + + + + + + + + + + + + + +
+ ${this.hass.localize("ui.panel.config.users.editor.id")} + ${user.id}
+ ${this.hass.localize("ui.panel.config.users.editor.owner")} + ${user.is_owner}
+ ${this.hass.localize("ui.panel.config.users.editor.active")} + ${user.is_active}
+ ${this.hass.localize( + "ui.panel.config.users.editor.system_generated" + )} + ${user.system_generated}
+
+
+ +
+ + ${this.hass!.localize("ui.panel.config.users.editor.delete_user")} + + ${user.system_generated + ? html` + ${this.hass.localize( + "ui.panel.config.users.editor.system_generated_users_not_removable" + )} + ` + : ""} +
+ + ${this.hass!.localize("ui.panel.config.users.editor.update_user")} + +
+ `; + } + + private _nameChanged(ev: PolymerChangedEvent) { + this._error = undefined; + this._name = ev.detail.value; + } + + private async _handleGroupChange(ev): Promise { + this._group = ev.detail.item.getAttribute("group-id"); + } + + private async _updateEntry() { + this._submitting = true; + try { + await this._params!.updateEntry({ + name: this._name.trim(), + group_ids: [this._group!], + }); + this._close(); + } catch (err) { + this._error = err?.message || "Unknown error"; + } finally { + this._submitting = false; + } + } + + private async _deleteEntry() { + this._submitting = true; + try { + if (await this._params!.removeEntry()) { + this._params = undefined; + } + } finally { + this._submitting = false; + } + } + + private _close(): void { + this._params = undefined; + } + + static get styles(): CSSResult[] { + return [ + haStyleDialog, + css` + ha-dialog { + --mdc-dialog-min-width: 500px; + } + table { + width: 100%; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-user-detail": DialogUserDetail; + } +} diff --git a/src/panels/config/users/ha-config-user-picker.js b/src/panels/config/users/ha-config-user-picker.js deleted file mode 100644 index c7a78b581e..0000000000 --- a/src/panels/config/users/ha-config-user-picker.js +++ /dev/null @@ -1,166 +0,0 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../../../layouts/hass-tabs-subpage"; -import "../../../components/ha-icon-next"; -import "../../../components/ha-card"; -import "../../../components/ha-fab"; - -import LocalizeMixin from "../../../mixins/localize-mixin"; -import NavigateMixin from "../../../mixins/navigate-mixin"; -import { EventsMixin } from "../../../mixins/events-mixin"; - -import { computeRTL } from "../../../common/util/compute_rtl"; -import { configSections } from "../ha-panel-config"; - -let registeredDialog = false; - -/* - * @appliesMixin LocalizeMixin - * @appliesMixin NavigateMixin - * @appliesMixin EventsMixin - */ -class HaUserPicker extends EventsMixin( - NavigateMixin(LocalizeMixin(PolymerElement)) -) { - static get template() { - return html` - - - - - - - - - - `; - } - - static get properties() { - return { - hass: Object, - users: Array, - isWide: Boolean, - narrow: Boolean, - route: Object, - rtl: { - type: Boolean, - reflectToAttribute: true, - computed: "_computeRTL(hass)", - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - - if (!registeredDialog) { - registeredDialog = true; - this.fire("register-dialog", { - dialogShowEvent: "show-add-user", - dialogTag: "ha-dialog-add-user", - dialogImport: () => - import( - /* webpackChunkName: "ha-dialog-add-user" */ "./ha-dialog-add-user" - ), - }); - } - } - - _withDefault(value, defaultValue) { - return value || defaultValue; - } - - _computeUrl(user) { - return `/config/users/${user.id}`; - } - - _computeGroup(localize, user) { - return localize(`groups.${user.group_ids[0]}`); - } - - _computeRTL(hass) { - return computeRTL(hass); - } - - _computeTabs() { - return configSections.persons; - } - - _addUser() { - this.fire("show-add-user", { - hass: this.hass, - dialogClosedCallback: async ({ userId }) => { - this.fire("reload-users"); - if (userId) this.navigate(`/config/users/${userId}`); - }, - }); - } -} - -customElements.define("ha-config-user-picker", HaUserPicker); diff --git a/src/panels/config/users/ha-config-users.js b/src/panels/config/users/ha-config-users.js deleted file mode 100644 index 7663a2ca6a..0000000000 --- a/src/panels/config/users/ha-config-users.js +++ /dev/null @@ -1,109 +0,0 @@ -import "@polymer/app-route/app-route"; -import { timeOut } from "@polymer/polymer/lib/utils/async"; -import { Debouncer } from "@polymer/polymer/lib/utils/debounce"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import NavigateMixin from "../../../mixins/navigate-mixin"; - -import "./ha-config-user-picker"; -import "./ha-user-editor"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { fetchUsers } from "../../../data/user"; - -/* - * @appliesMixin NavigateMixin - */ -class HaConfigUsers extends NavigateMixin(PolymerElement) { - static get template() { - return html` - - - - - `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - route: { - type: Object, - observer: "_checkRoute", - }, - _routeData: Object, - _user: { - type: Object, - value: null, - }, - _users: { - type: Array, - value: null, - }, - }; - } - - ready() { - super.ready(); - this._loadData(); - this.addEventListener("reload-users", () => this._loadData()); - } - - _handlePickUser(ev) { - this._user = ev.detail.user; - } - - _checkRoute(route) { - // prevent list getting under toolbar - fireEvent(this, "iron-resize"); - - this._debouncer = Debouncer.debounce( - this._debouncer, - timeOut.after(0), - () => { - if (route.path === "") { - this.navigate(`${route.prefix}/picker`, true); - } - } - ); - } - - _computeUser(users, userId) { - return users && users.filter((u) => u.id === userId)[0]; - } - - _equals(a, b) { - return a === b; - } - - async _loadData() { - this._users = await fetchUsers(this.hass); - } -} - -customElements.define("ha-config-users", HaConfigUsers); diff --git a/src/panels/config/users/ha-config-users.ts b/src/panels/config/users/ha-config-users.ts new file mode 100644 index 0000000000..2609d7a1b3 --- /dev/null +++ b/src/panels/config/users/ha-config-users.ts @@ -0,0 +1,192 @@ +import "../../../layouts/hass-tabs-subpage-data-table"; +import "../../../components/ha-fab"; + +import { computeRTL } from "../../../common/util/compute_rtl"; +import { configSections } from "../ha-panel-config"; +import { + LitElement, + property, + css, + PropertyValues, + customElement, +} from "lit-element"; +import { HomeAssistant, Route } from "../../../types"; +import { html } from "lit-html"; +import { HASSDomEvent } from "../../../common/dom/fire_event"; +import { User, fetchUsers, updateUser, deleteUser } from "../../../data/user"; +import memoizeOne from "memoize-one"; +import { + DataTableColumnContainer, + RowClickedEvent, +} from "../../../components/data-table/ha-data-table"; +import { showUserDetailDialog } from "./show-dialog-user-detail"; +import { showAddUserDialog } from "./show-dialog-add-user"; +import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; + +@customElement("ha-config-users") +export class HaConfigUsers extends LitElement { + @property() public hass!: HomeAssistant; + @property() public _users: User[] = []; + @property() public isWide!: boolean; + @property() public narrow!: boolean; + @property() public route!: Route; + + private _columns = memoizeOne( + (_language): DataTableColumnContainer => { + return { + name: { + title: this.hass.localize( + "ui.panel.config.users.picker.headers.name" + ), + sortable: true, + filterable: true, + direction: "asc", + grows: true, + template: (name) => html` + ${name || + this.hass!.localize("ui.panel.config.users.editor.unnamed_user")} + `, + }, + group_ids: { + title: this.hass.localize( + "ui.panel.config.users.picker.headers.group" + ), + sortable: true, + filterable: true, + width: "25%", + template: (groupIds) => html` + ${this.hass.localize(`groups.${groupIds[0]}`)} + `, + }, + system_generated: { + title: this.hass.localize( + "ui.panel.config.users.picker.headers.system" + ), + type: "icon", + width: "10%", + sortable: true, + filterable: true, + template: (generated) => html` + ${generated + ? html` + + ` + : ""} + `, + }, + }; + } + ); + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + this._fetchUsers(); + } + + protected render() { + return html` + + + + `; + } + + private async _fetchUsers() { + this._users = await fetchUsers(this.hass); + } + + private _editUser(ev: HASSDomEvent) { + const id = ev.detail.id; + const entry = this._users.find((user) => user.id === id); + + if (!entry) { + return; + } + + showUserDetailDialog(this, { + entry, + updateEntry: async (values) => { + const updated = await updateUser(this.hass!, entry!.id, values); + this._users = this._users!.map((ent) => + ent === entry ? updated.user : ent + ); + }, + removeEntry: async () => { + if ( + !(await showConfirmationDialog(this, { + title: this.hass!.localize( + "ui.panel.config.users.editor.confirm_user_deletion", + "name", + entry.name + ), + dismissText: this.hass!.localize("ui.common.no"), + confirmText: this.hass!.localize("ui.common.yes"), + })) + ) { + return false; + } + + try { + await deleteUser(this.hass!, entry!.id); + this._users = this._users!.filter((ent) => ent !== entry); + return true; + } catch (err) { + return false; + } + }, + }); + } + + private _addUser() { + showAddUserDialog(this, { + userAddedCallback: async (user: User) => { + if (user) { + this._users = { ...this._users, ...user }; + } + }, + }); + } + + static get styles() { + return css` + ha-fab { + position: fixed; + bottom: 16px; + right: 16px; + z-index: 1; + } + ha-fab[is-wide] { + bottom: 24px; + right: 24px; + } + ha-fab[rtl] { + right: auto; + left: 16px; + } + ha-fab[narrow] { + bottom: 84px; + } + ha-fab[rtl][is-wide] { + bottom: 24px; + right: auto; + left: 24px; + } + `; + } +} diff --git a/src/panels/config/users/ha-dialog-add-user.js b/src/panels/config/users/ha-dialog-add-user.js deleted file mode 100644 index dd710bb8bb..0000000000 --- a/src/panels/config/users/ha-dialog-add-user.js +++ /dev/null @@ -1,201 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/paper-spinner/paper-spinner"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../../../components/dialog/ha-paper-dialog"; -import "../../../resources/ha-style"; - -import LocalizeMixin from "../../../mixins/localize-mixin"; - -/* - * @appliesMixin LocalizeMixin - */ -class HaDialogAddUser extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - -

[[localize('ui.panel.config.users.add_user.caption')]]

-
- - - - -
-
- - -
-
- `; - } - - static get properties() { - return { - _hass: Object, - _dialogClosedCallback: Function, - - _loading: { - type: Boolean, - value: false, - }, - - // Error message when can't talk to server etc - _errorMsg: String, - - _opened: { - type: Boolean, - value: false, - }, - - _name: String, - _username: String, - _password: String, - }; - } - - ready() { - super.ready(); - this.addEventListener("keypress", (ev) => { - if (ev.keyCode === 13) { - this._createUser(ev); - } - }); - } - - showDialog({ hass, dialogClosedCallback }) { - this.hass = hass; - this._dialogClosedCallback = dialogClosedCallback; - this._loading = false; - this._opened = true; - setTimeout(() => this.shadowRoot.querySelector("paper-input").focus(), 0); - } - - _maybePopulateUsername() { - if (this._username) return; - - const parts = this._name.split(" "); - - if (parts.length) { - this._username = parts[0].toLowerCase(); - } - } - - async _createUser(ev) { - ev.preventDefault(); - if (!this._name || !this._username || !this._password) return; - - this._loading = true; - this._errorMsg = null; - - let userId; - - try { - const userResponse = await this.hass.callWS({ - type: "config/auth/create", - name: this._name, - }); - userId = userResponse.user.id; - } catch (err) { - this._loading = false; - this._errorMsg = err.code; - return; - } - - try { - await this.hass.callWS({ - type: "config/auth_provider/homeassistant/create", - user_id: userId, - username: this._username, - password: this._password, - }); - } catch (err) { - this._loading = false; - this._errorMsg = err.code; - await this.hass.callWS({ - type: "config/auth/delete", - user_id: userId, - }); - return; - } - - this._dialogDone(userId); - } - - _dialogDone(userId) { - this._dialogClosedCallback({ userId }); - - this.setProperties({ - _errorMsg: null, - _username: "", - _password: "", - _dialogClosedCallback: null, - _opened: false, - }); - } - - _equals(a, b) { - return a === b; - } - - _openedChanged(ev) { - // Closed dialog by clicking on the overlay - // Check against dialogClosedCallback to make sure we didn't change - // programmatically - if (this._dialogClosedCallback && !ev.detail.value) { - this._dialogDone(); - } - } -} - -customElements.define("ha-dialog-add-user", HaDialogAddUser); diff --git a/src/panels/config/users/ha-user-editor.ts b/src/panels/config/users/ha-user-editor.ts deleted file mode 100644 index 2aa50c413c..0000000000 --- a/src/panels/config/users/ha-user-editor.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { - LitElement, - TemplateResult, - html, - customElement, - CSSResultArray, - css, - property, -} from "lit-element"; -import { until } from "lit-html/directives/until"; -import "@material/mwc-button"; - -import "../../../layouts/hass-tabs-subpage"; -import { haStyle } from "../../../resources/styles"; -import "../../../components/ha-card"; -import { HomeAssistant, Route } from "../../../types"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { navigate } from "../../../common/navigate"; -import { - User, - deleteUser, - updateUser, - SYSTEM_GROUP_ID_USER, - SYSTEM_GROUP_ID_ADMIN, -} from "../../../data/user"; -import { showSaveSuccessToast } from "../../../util/toast-saved-success"; -import { - showAlertDialog, - showConfirmationDialog, - showPromptDialog, -} from "../../../dialogs/generic/show-dialog-box"; -import { configSections } from "../ha-panel-config"; - -declare global { - interface HASSDomEvents { - "reload-users": undefined; - } -} - -const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN]; - -@customElement("ha-user-editor") -class HaUserEditor extends LitElement { - @property() public hass?: HomeAssistant; - @property() public user?: User; - @property() public narrow?: boolean; - @property() public route!: Route; - - protected render(): TemplateResult { - const hass = this.hass; - const user = this.user; - if (!hass || !user) { - return html``; - } - - return html` - - - - - - - - - - - - - - - - ${user.group_ids[0] === SYSTEM_GROUP_ID_USER - ? html` - - - - ` - : ""} - - - - - - - - - -
${hass.localize("ui.panel.config.users.editor.id")}${user.id}
${hass.localize("ui.panel.config.users.editor.owner")}${user.is_owner}
${hass.localize("ui.panel.config.users.editor.group")} - -
- The users group is a work in progress. The user will be - unable to administer the instance via the UI. We're still - auditing all management API endpoints to ensure that they - correctly limit access to administrators. -
${hass.localize("ui.panel.config.users.editor.active")}${user.is_active}
- ${hass.localize( - "ui.panel.config.users.editor.system_generated" - )} - ${user.system_generated}
- -
- - ${hass.localize("ui.panel.config.users.editor.rename_user")} - - - ${hass.localize("ui.panel.config.users.editor.delete_user")} - - ${user.system_generated - ? html` - ${hass.localize( - "ui.panel.config.users.editor.system_generated_users_not_removable" - )} - ` - : ""} -
-
-
- `; - } - - private get _name() { - return ( - this.user && - (this.user.name || - this.hass!.localize("ui.panel.config.users.editor.unnamed_user")) - ); - } - - private async _handleRenameUser(newName?: string) { - if (newName === null || newName === this.user!.name) { - return; - } - - try { - await updateUser(this.hass!, this.user!.id, { - name: newName, - }); - fireEvent(this, "reload-users"); - } catch (err) { - showAlertDialog(this, { - text: `${this.hass!.localize( - "ui.panel.config.users.editor.user_rename_failed" - )} ${err.message}`, - }); - } - } - - private async _handlePromptRenameUser(ev): Promise { - ev.currentTarget.blur(); - showPromptDialog(this, { - title: this.hass!.localize("ui.panel.config.users.editor.enter_new_name"), - defaultValue: this.user!.name, - inputLabel: this.hass!.localize("ui.panel.config.users.add_user.name"), - confirm: (text) => this._handleRenameUser(text), - }); - } - - private async _handleGroupChange(ev): Promise { - const selectEl = ev.currentTarget as HTMLSelectElement; - const newGroup = selectEl.value; - try { - await updateUser(this.hass!, this.user!.id, { - group_ids: [newGroup], - }); - showSaveSuccessToast(this, this.hass!); - fireEvent(this, "reload-users"); - } catch (err) { - showAlertDialog(this, { - text: `${this.hass!.localize( - "ui.panel.config.users.editor.group_update_failed" - )} ${err.message}`, - }); - selectEl.value = this.user!.group_ids[0]; - } - } - - private async _deleteUser() { - try { - await deleteUser(this.hass!, this.user!.id); - } catch (err) { - showAlertDialog(this, { - text: err.code, - }); - return; - } - fireEvent(this, "reload-users"); - navigate(this, "/config/users"); - } - - private async _promptDeleteUser(_ev): Promise { - showConfirmationDialog(this, { - text: this.hass!.localize( - "ui.panel.config.users.editor.confirm_user_deletion", - "name", - this._name - ), - confirm: () => this._deleteUser(), - }); - } - - static get styles(): CSSResultArray { - return [ - haStyle, - css` - .card-actions { - display: flex; - justify-content: space-between; - align-items: center; - } - ha-card { - max-width: 600px; - margin: 16px auto 16px; - } - hass-subpage ha-card:first-of-type { - direction: ltr; - } - table { - width: 100%; - } - td { - vertical-align: top; - } - .user-experiment { - padding: 8px 0; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-user-editor": HaUserEditor; - } -} diff --git a/src/panels/config/users/show-dialog-add-user.ts b/src/panels/config/users/show-dialog-add-user.ts new file mode 100644 index 0000000000..5ff0e375e9 --- /dev/null +++ b/src/panels/config/users/show-dialog-add-user.ts @@ -0,0 +1,20 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { User } from "../../../data/user"; + +export interface AddUserDialogParams { + userAddedCallback: (user: User) => void; +} + +export const loadAddUserDialog = () => + import(/* webpackChunkName: "add-user-dialog" */ "./dialog-add-user"); + +export const showAddUserDialog = ( + element: HTMLElement, + dialogParams: AddUserDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-add-user", + dialogImport: loadAddUserDialog, + dialogParams, + }); +}; diff --git a/src/panels/config/users/show-dialog-user-detail.ts b/src/panels/config/users/show-dialog-user-detail.ts new file mode 100644 index 0000000000..0f04236a56 --- /dev/null +++ b/src/panels/config/users/show-dialog-user-detail.ts @@ -0,0 +1,22 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { User, UpdateUserParams } from "../../../data/user"; + +export interface UserDetailDialogParams { + entry: User; + updateEntry: (updates: Partial) => Promise; + removeEntry: () => Promise; +} + +export const loadUserDetailDialog = () => + import(/* webpackChunkName: "user-detail-dialog" */ "./dialog-user-detail"); + +export const showUserDetailDialog = ( + element: HTMLElement, + detailParams: UserDetailDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-user-detail", + dialogImport: loadUserDetailDialog, + dialogParams: detailParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index 78753b6138..51396598d1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1229,6 +1229,7 @@ "learn_more": "Learn more about scripts", "no_scripts": "We couldn’t find any editable scripts", "add_script": "Add script", + "show_info": "Show info about script", "trigger_script": "Trigger script", "edit_script": "Edit script", "headers": { @@ -1664,16 +1665,16 @@ "caption": "Users", "description": "Manage users", "picker": { - "title": "Users", - "system_generated": "System generated" + "headers": { "name": "Name", "group": "Group", "system": "System" } }, "editor": { "caption": "View user", - "rename_user": "Rename user", + "name": "Name", "change_password": "Change password", "activate_user": "Activate user", "deactivate_user": "Deactivate user", "delete_user": "Delete user", + "update_user": "Update", "id": "ID", "owner": "Owner", "group": "Group", @@ -1681,9 +1682,6 @@ "system_generated": "System generated", "system_generated_users_not_removable": "Unable to remove system generated users.", "unnamed_user": "Unnamed User", - "enter_new_name": "Enter new name", - "user_rename_failed": "User rename failed:", - "group_update_failed": "Group update failed:", "confirm_user_deletion": "Are you sure you want to delete {name}?" }, "add_user": {