diff --git a/src/data/user.ts b/src/data/user.ts index 7b108b3e7c..c4afd4aa68 100644 --- a/src/data/user.ts +++ b/src/data/user.ts @@ -13,6 +13,7 @@ export interface User { name: string; is_owner: boolean; is_active: boolean; + local_only: boolean; system_generated: boolean; group_ids: string[]; credentials: Credential[]; @@ -22,6 +23,7 @@ export interface UpdateUserParams { name?: User["name"]; is_active?: User["is_active"]; group_ids?: User["group_ids"]; + local_only?: boolean; } export const fetchUsers = async (hass: HomeAssistant) => @@ -33,12 +35,14 @@ export const createUser = async ( hass: HomeAssistant, name: string, // eslint-disable-next-line: variable-name - group_ids?: User["group_ids"] + group_ids?: User["group_ids"], + local_only?: boolean ) => hass.callWS<{ user: User }>({ type: "config/auth/create", name, group_ids, + local_only, }); export const updateUser = async ( diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index 06f1f05841..2724d8ef2e 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -51,6 +51,8 @@ class DialogPersonDetail extends LitElement { @state() private _isAdmin?: boolean; + @state() private _localOnly?: boolean; + @state() private _deviceTrackers!: string[]; @state() private _picture!: string | null; @@ -83,12 +85,14 @@ class DialogPersonDetail extends LitElement { ? this._params.users.find((user) => user.id === this._userId) : undefined; this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN); + this._localOnly = this._user?.local_only; } else { this._personExists = false; this._name = ""; this._userId = undefined; this._user = undefined; this._isAdmin = undefined; + this._localOnly = undefined; this._deviceTrackers = []; this._picture = null; } @@ -152,19 +156,31 @@ class DialogPersonDetail extends LitElement { ${this._user ? html` - - - ` + + + + + + + ` : ""} ${this._deviceTrackersAvailable(this.hass) ? html` @@ -266,10 +282,14 @@ class DialogPersonDetail extends LitElement { this._name = ev.detail.value; } - private async _adminChanged(ev): Promise { + private _adminChanged(ev): void { this._isAdmin = ev.target.checked; } + private _localOnlyChanged(ev): void { + this._localOnly = ev.target.checked; + } + private async _allowLoginChanged(ev): Promise { const target = ev.target; if (target.checked) { @@ -281,6 +301,7 @@ class DialogPersonDetail extends LitElement { 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(); } }, @@ -373,13 +394,16 @@ class DialogPersonDetail extends LitElement { try { if ( (this._userId && this._name !== this._params!.entry?.name) || - this._isAdmin !== this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN) + this._isAdmin !== + this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN) || + this._localOnly !== this._user?.local_only ) { await updateUser(this.hass!, this._userId!, { name: this._name.trim(), group_ids: [ this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER, ], + local_only: this._localOnly, }); this._params?.refreshUsers(); } diff --git a/src/panels/config/users/dialog-add-user.ts b/src/panels/config/users/dialog-add-user.ts index 462dd46fde..65d11ac262 100644 --- a/src/panels/config/users/dialog-add-user.ts +++ b/src/panels/config/users/dialog-add-user.ts @@ -48,6 +48,8 @@ export class DialogAddUser extends LitElement { @state() private _isAdmin?: boolean; + @state() private _localOnly?: boolean; + @state() private _allowChangeName = true; public showDialog(params: AddUserDialogParams) { @@ -57,6 +59,7 @@ export class DialogAddUser extends LitElement { this._password = ""; this._passwordConfirm = ""; this._isAdmin = false; + this._localOnly = false; this._error = undefined; this._loading = false; @@ -153,14 +156,32 @@ export class DialogAddUser extends LitElement { "ui.panel.config.users.add_user.password_not_match" )} > - - - - - +
+ + + + +
+
+ + + + +
${!this._isAdmin ? html`
@@ -218,6 +239,10 @@ export class DialogAddUser extends LitElement { this._isAdmin = ev.target.checked; } + private _localOnlyChanged(ev): void { + this._localOnly = ev.target.checked; + } + private async _createUser(ev) { ev.preventDefault(); if (!this._name || !this._username || !this._password) { @@ -229,9 +254,12 @@ export class DialogAddUser extends LitElement { let user: User; try { - const userResponse = await createUser(this.hass, this._name, [ - this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER, - ]); + const userResponse = await createUser( + this.hass, + this._name, + [this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER], + this._localOnly + ); user = userResponse.user; } catch (err: any) { this._loading = false; @@ -266,8 +294,9 @@ export class DialogAddUser extends LitElement { --mdc-dialog-max-width: 500px; --dialog-z-index: 10; } - ha-switch { - margin-top: 8px; + .row { + display: flex; + padding: 8px 0; } `, ]; diff --git a/src/panels/config/users/dialog-user-detail.ts b/src/panels/config/users/dialog-user-detail.ts index f90f9c979d..3809131a65 100644 --- a/src/panels/config/users/dialog-user-detail.ts +++ b/src/panels/config/users/dialog-user-detail.ts @@ -30,6 +30,8 @@ class DialogUserDetail extends LitElement { @state() private _isAdmin?: boolean; + @state() private _localOnly?: boolean; + @state() private _isActive?: boolean; @state() private _error?: string; @@ -43,6 +45,7 @@ class DialogUserDetail extends LitElement { this._error = undefined; this._name = params.entry.name || ""; this._isAdmin = params.entry.group_ids.includes(SYSTEM_GROUP_ID_ADMIN); + this._localOnly = params.entry.local_only; this._isActive = params.entry.is_active; await this.updateComplete; } @@ -95,6 +98,20 @@ class DialogUserDetail extends LitElement { @value-changed=${this._nameChanged} label=${this.hass!.localize("ui.panel.config.users.editor.name")} > +
+ + + + +
{ + private _adminChanged(ev): void { this._isAdmin = ev.target.checked; } - private async _activeChanged(ev): Promise { + private _localOnlyChanged(ev): void { + this._localOnly = ev.target.checked; + } + + private _activeChanged(ev): void { this._isActive = ev.target.checked; } @@ -215,6 +236,7 @@ class DialogUserDetail extends LitElement { group_ids: [ this._isAdmin ? SYSTEM_GROUP_ID_ADMIN : SYSTEM_GROUP_ID_USER, ], + local_only: this._localOnly, }); this._close(); } catch (err: any) { diff --git a/src/panels/config/users/ha-config-users.ts b/src/panels/config/users/ha-config-users.ts index 84b0ca9f11..dfff21062c 100644 --- a/src/panels/config/users/ha-config-users.ts +++ b/src/panels/config/users/ha-config-users.ts @@ -90,7 +90,7 @@ export class HaConfigUsers extends LitElement { width: "80px", template: (is_active) => is_active - ? html` ` + ? html`` : "", }, system_generated: { @@ -103,9 +103,20 @@ export class HaConfigUsers extends LitElement { width: "160px", template: (generated) => generated - ? html` ` + ? html`` : "", }, + local_only: { + title: this.hass.localize( + "ui.panel.config.users.picker.headers.local" + ), + type: "icon", + sortable: true, + filterable: true, + width: "160px", + template: (local) => + local ? html`` : "", + }, }; return columns; diff --git a/src/translations/en.json b/src/translations/en.json index bd2574bde1..b6c5d18cd8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2297,6 +2297,7 @@ "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%]", + "local_only": "[%key:ui::panel::config::users::editor::local_only%]", "allow_login": "Allow person to login" } }, @@ -2456,7 +2457,8 @@ "group": "Group", "system": "System generated", "is_active": "Active", - "is_owner": "Owner" + "is_owner": "Owner", + "local": "Local only" }, "add_user": "Add user" }, @@ -2476,6 +2478,7 @@ "admin": "Administrator", "group": "Group", "active": "Active", + "local_only": "Can only login from the local network", "system_generated": "System generated", "system_generated_users_not_removable": "Unable to remove system generated users.", "system_generated_users_not_editable": "Unable to update system generated users.", @@ -2488,6 +2491,7 @@ "password": "Password", "password_confirm": "Confirm Password", "password_not_match": "Passwords don't match", + "local_only": "Local only", "create": "Create" } },