- [[user.id]]
+ [[_computeGroup(localize, user)]]
- System Generated
@@ -124,6 +124,10 @@ class HaUserPicker extends EventsMixin(
return `/config/users/${user.id}`;
}
+ _computeGroup(localize, user) {
+ return localize(`groups.${user.group_ids[0]}`);
+ }
+
_computeRTL(hass) {
return computeRTL(hass);
}
diff --git a/src/panels/config/users/ha-config-users.js b/src/panels/config/users/ha-config-users.js
index 16cfe6d5b9..6778f282cb 100644
--- a/src/panels/config/users/ha-config-users.js
+++ b/src/panels/config/users/ha-config-users.js
@@ -9,7 +9,7 @@ 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/auth";
+import { fetchUsers } from "../../../data/user";
/*
* @appliesMixin NavigateMixin
diff --git a/src/panels/config/users/ha-user-editor.js b/src/panels/config/users/ha-user-editor.js
deleted file mode 100644
index 15e1e9ec74..0000000000
--- a/src/panels/config/users/ha-user-editor.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import "@material/mwc-button";
-import "@polymer/paper-card/paper-card";
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-
-import "../../../layouts/hass-subpage";
-import LocalizeMixin from "../../../mixins/localize-mixin";
-import NavigateMixin from "../../../mixins/navigate-mixin";
-import EventsMixin from "../../../mixins/events-mixin";
-
-/*
- * @appliesMixin LocalizeMixin
- * @appliesMixin NavigateMixin
- * @appliesMixin EventsMixin
- */
-class HaUserEditor extends EventsMixin(
- NavigateMixin(LocalizeMixin(PolymerElement))
-) {
- static get template() {
- return html`
-
-
-
-
-
-
- ID
- [[user.id]]
-
-
- Owner
- [[user.is_owner]]
-
-
- Active
- [[user.is_active]]
-
-
- System generated
- [[user.system_generated]]
-
-
-
-
-
-
- [[localize('ui.panel.config.users.editor.delete_user')]]
-
-
- Unable to remove system generated users.
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: Object,
- user: Object,
- };
- }
-
- _computeName(user) {
- return user && (user.name || "Unnamed user");
- }
-
- async _deleteUser(ev) {
- if (
- !confirm(
- `Are you sure you want to delete ${this._computeName(this.user)}`
- )
- ) {
- ev.target.blur();
- return;
- }
- try {
- await this.hass.callWS({
- type: "config/auth/delete",
- user_id: this.user.id,
- });
- } catch (err) {
- alert(err.code);
- return;
- }
- this.fire("reload-users");
- this.navigate("/config/users");
- }
-}
-
-customElements.define("ha-user-editor", HaUserEditor);
diff --git a/src/panels/config/users/ha-user-editor.ts b/src/panels/config/users/ha-user-editor.ts
new file mode 100644
index 0000000000..245902d84f
--- /dev/null
+++ b/src/panels/config/users/ha-user-editor.ts
@@ -0,0 +1,217 @@
+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-subpage";
+import { haStyle } from "../../../resources/styles";
+import "../../../components/ha-card";
+import { HomeAssistant } 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";
+
+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;
+
+ protected render(): TemplateResult | void {
+ const hass = this.hass;
+ const user = this.user;
+ if (!hass || !user) {
+ return html``;
+ }
+
+ return html`
+
+
+
+
+ ID
+ ${user.id}
+
+
+ Owner
+ ${user.is_owner}
+
+
+ Group
+
+ user.group_ids[0])
+ )}
+ >
+ ${GROUPS.map(
+ (groupId) => html`
+
+ ${hass.localize(`groups.${groupId}`)}
+
+ `
+ )}
+
+
+
+ ${user.group_ids[0] === 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.
+
+
+ `
+ : ""}
+
+
+ Active
+ ${user.is_active}
+
+
+ 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`
+ Unable to remove system generated users.
+ `
+ : ""}
+
+
+
+ `;
+ }
+
+ private get _name() {
+ return this.user && (this.user.name || "Unnamed user");
+ }
+
+ private async _handleRenameUser(ev): Promise
{
+ ev.currentTarget.blur();
+ const newName = prompt("New name?", this.user!.name);
+ if (newName === null || newName === this.user!.name) {
+ return;
+ }
+
+ try {
+ await updateUser(this.hass!, this.user!.id, {
+ name: newName,
+ });
+ fireEvent(this, "reload-users");
+ } catch (err) {
+ alert(`User rename failed: ${err.message}`);
+ }
+ }
+
+ 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],
+ });
+ fireEvent(this, "reload-users");
+ } catch (err) {
+ alert(`Group update failed: ${err.message}`);
+ selectEl.value = this.user!.group_ids[0];
+ }
+ }
+
+ private async _deleteUser(ev): Promise {
+ if (!confirm(`Are you sure you want to delete ${this._name}`)) {
+ ev.target.blur();
+ return;
+ }
+ try {
+ await deleteUser(this.hass!, this.user!.id);
+ } catch (err) {
+ alert(err.code);
+ return;
+ }
+ fireEvent(this, "reload-users");
+ navigate(this, "/config/users");
+ }
+
+ static get styles(): CSSResultArray {
+ return [
+ haStyle,
+ css`
+ ha-card {
+ display: block;
+ max-width: 600px;
+ margin: 0 auto 16px;
+ }
+ ha-card:first-child {
+ margin-top: 16px;
+ }
+ ha-card:last-child {
+ margin-bottom: 16px;
+ }
+ .card-content {
+ padding: 0 16px 16px;
+ }
+ .card-actions {
+ padding: 0 8px;
+ }
+ 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/translations/en.json b/src/translations/en.json
index 3fe3ab60d6..d28a834f45 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -355,6 +355,11 @@
"not_home": "[%key:state::person::not_home%]"
}
},
+ "groups": {
+ "system-admin": "Administrators",
+ "system-users": "Users",
+ "system-read-only": "Read-Only Users"
+ },
"ui": {
"auth_store": {
"ask": "Do you want to save this login?",
diff --git a/src/types.ts b/src/types.ts
index 946f5e8137..bcefb5418a 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -57,6 +57,7 @@ export interface MFAModule {
export interface CurrentUser {
id: string;
is_owner: boolean;
+ is_admin: boolean;
name: string;
credentials: Credential[];
mfa_modules: MFAModule[];