mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-15 21:36:36 +00:00
Add support for local only users (#10784)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
parent
0bcb4d0e09
commit
a54a2a54f8
@ -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 (
|
||||
|
@ -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`<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.admin"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.disabled=${this._user.system_generated ||
|
||||
this._user.is_owner}
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.local_only"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>`
|
||||
<ha-switch
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.person.detail.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`
|
||||
@ -266,10 +282,14 @@ class DialogPersonDetail extends LitElement {
|
||||
this._name = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _adminChanged(ev): Promise<void> {
|
||||
private _adminChanged(ev): void {
|
||||
this._isAdmin = ev.target.checked;
|
||||
}
|
||||
|
||||
private _localOnlyChanged(ev): void {
|
||||
this._localOnly = ev.target.checked;
|
||||
}
|
||||
|
||||
private async _allowLoginChanged(ev): Promise<void> {
|
||||
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();
|
||||
}
|
||||
|
@ -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"
|
||||
)}
|
||||
></paper-input>
|
||||
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch .checked=${this._isAdmin} @change=${this._adminChanged}>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<div class="row">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_only"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<div class="row">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${!this._isAdmin
|
||||
? html`
|
||||
<br />
|
||||
@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -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")}
|
||||
></paper-input>
|
||||
<div class="row">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_only"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<div class="row">
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
@ -198,11 +215,15 @@ class DialogUserDetail extends LitElement {
|
||||
this._name = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _adminChanged(ev): Promise<void> {
|
||||
private _adminChanged(ev): void {
|
||||
this._isAdmin = ev.target.checked;
|
||||
}
|
||||
|
||||
private async _activeChanged(ev): Promise<void> {
|
||||
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) {
|
||||
|
@ -90,7 +90,7 @@ export class HaConfigUsers extends LitElement {
|
||||
width: "80px",
|
||||
template: (is_active) =>
|
||||
is_active
|
||||
? html`<ha-svg-icon .path=${mdiCheck}> </ha-svg-icon>`
|
||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||
: "",
|
||||
},
|
||||
system_generated: {
|
||||
@ -103,9 +103,20 @@ export class HaConfigUsers extends LitElement {
|
||||
width: "160px",
|
||||
template: (generated) =>
|
||||
generated
|
||||
? html`<ha-svg-icon .path=${mdiCheck}> </ha-svg-icon>`
|
||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||
: "",
|
||||
},
|
||||
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`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` : "",
|
||||
},
|
||||
};
|
||||
|
||||
return columns;
|
||||
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user