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