Add backup location selector (#16567)

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
Bram Kragten 2023-05-23 13:35:31 +02:00 committed by GitHub
parent 22dc757382
commit a5b5e61ed4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 259 additions and 29 deletions

View File

@ -5,13 +5,15 @@ import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event";
import { stringCompare } from "../common/string/compare";
import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import { ValueChangedEvent, HomeAssistant } from "../types";
import { HaComboBox } from "./ha-combo-box";
import { HomeAssistant, ValueChangedEvent } from "../types";
import "./ha-alert";
import "./ha-combo-box";
import type { HaComboBox } from "./ha-combo-box";
import "./ha-list-item";
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
item
) => html`<mwc-list-item twoline graphic="icon">
) => html`<ha-list-item twoline graphic="icon">
<span>${item.name}</span>
<span slot="secondary">${item.slug}</span>
${item.icon
@ -21,7 +23,7 @@ const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
.src="/api/hassio/addons/${item.slug}/icon"
/>`
: ""}
</mwc-list-item>`;
</ha-list-item>`;
@customElement("ha-addon-picker")
class HaAddonPicker extends LitElement {
@ -41,6 +43,8 @@ class HaAddonPicker extends LitElement {
@query("ha-combo-box") private _comboBox!: HaComboBox;
@state() private _error?: string;
public open() {
this._comboBox?.open();
}
@ -57,6 +61,9 @@ class HaAddonPicker extends LitElement {
if (!this._addons) {
return nothing;
}
if (this._error) {
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
}
return html`
<ha-combo-box
.hass=${this.hass}
@ -87,24 +94,14 @@ class HaAddonPicker extends LitElement {
stringCompare(a.name, b.name, this.hass.locale.language)
);
} else {
showAlertDialog(this, {
title: this.hass.localize(
"ui.components.addon-picker.error.no_supervisor.title"
),
text: this.hass.localize(
"ui.components.addon-picker.error.no_supervisor.description"
),
});
this._error = this.hass.localize(
"ui.components.addon-picker.error.no_supervisor"
);
}
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.components.addon-picker.error.fetch_addons.title"
),
text: this.hass.localize(
"ui.components.addon-picker.error.fetch_addons.description"
),
});
this._error = this.hass.localize(
"ui.components.addon-picker.error.fetch_addons"
);
}
}

View File

@ -0,0 +1,179 @@
import { mdiBackupRestore, mdiHarddisk, mdiPlayBox } from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import {
fetchSupervisorMounts,
SupervisorMounts,
SupervisorMountType,
SupervisorMountUsage,
} from "../data/supervisor/mounts";
import { HomeAssistant } from "../types";
import "./ha-alert";
import "./ha-list-item";
import "./ha-select";
import type { HaSelect } from "./ha-select";
const __BACKUP_DATA_DISK__ = "/backup";
@customElement("ha-mount-picker")
class HaMountPicker extends LitElement {
public hass!: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = false;
@property() public usage?: SupervisorMountUsage;
@state() private _mounts?: SupervisorMounts;
@state() private _error?: string;
protected firstUpdated() {
this._getMounts();
}
protected render() {
if (!this._mounts) {
return nothing;
}
if (this._error) {
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
}
const dataDiskOption = html`<ha-list-item
graphic="icon"
.value=${__BACKUP_DATA_DISK__}
>
<span
>${this.hass.localize("ui.components.mount-picker.use_datadisk")}</span
>
<ha-svg-icon slot="graphic" .path=${mdiHarddisk}></ha-svg-icon>
</ha-list-item>`;
return html`
<ha-select
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.mount-picker.mount")
: this.label}
.value=${this._value}
.required=${this.required}
.disabled=${this.disabled}
.helper=${this.helper}
@selected=${this._mountChanged}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
>
${this.usage !== SupervisorMountUsage.MEDIA &&
(!this._mounts.default_backup_mount ||
this._mounts.default_backup_mount === __BACKUP_DATA_DISK__)
? dataDiskOption
: nothing}
${this._filterMounts(this._mounts, this.usage).map(
(mount) => html`<ha-list-item
twoline
graphic="icon"
.value=${mount.name}
>
<span>${mount.name}</span>
<span slot="secondary"
>${mount.server}${mount.port
? `:${mount.port}`
: nothing}${mount.type === SupervisorMountType.NFS
? mount.path
: ` :${mount.share}`}</span
>
<ha-svg-icon
slot="graphic"
.path=${mount.usage === SupervisorMountUsage.MEDIA
? mdiPlayBox
: mdiBackupRestore}
></ha-svg-icon>
</ha-list-item>`
)}
${this.usage !== SupervisorMountUsage.MEDIA &&
this._mounts.default_backup_mount
? dataDiskOption
: nothing}
</ha-select>
`;
}
private _filterMounts = memoizeOne(
(mounts: SupervisorMounts, usage: this["usage"]) => {
let filteredMounts = mounts.mounts.filter((mount) =>
[SupervisorMountType.CIFS, SupervisorMountType.NFS].includes(mount.type)
);
if (usage) {
filteredMounts = mounts.mounts.filter((mount) => mount.usage === usage);
}
return filteredMounts.sort((mountA, mountB) => {
if (mountA.name === mounts.default_backup_mount) {
return -1;
}
if (mountB.name === mounts.default_backup_mount) {
return 1;
}
return caseInsensitiveStringCompare(
mountA.name,
mountB.name,
this.hass.locale.language
);
});
}
);
private async _getMounts() {
try {
if (isComponentLoaded(this.hass, "hassio")) {
this._mounts = await fetchSupervisorMounts(this.hass);
} else {
this._error = this.hass.localize(
"ui.components.mount-picker.error.no_supervisor"
);
}
} catch (err: any) {
this._error = this.hass.localize(
"ui.components.mount-picker.error.fetch_mounts"
);
}
}
private get _value() {
return this.value || "";
}
private _mountChanged(ev: Event) {
ev.stopPropagation();
const target = ev.target as HaSelect;
const newValue = target.value;
if (newValue !== this._value) {
this._setValue(newValue);
}
}
private _setValue(value: string) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });
fireEvent(this, "change");
}, 0);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-mount-picker": HaMountPicker;
}
}

View File

@ -0,0 +1,46 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { BackupLocationSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../ha-mount-picker";
@customElement("ha-selector-backup_location")
export class HaBackupLocationSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: BackupLocationSelector;
@property() public value?: any;
@property() public label?: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
protected render() {
return html`<ha-mount-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.helper=${this.helper}
.disabled=${this.disabled}
.required=${this.required}
usage="backup"
></ha-mount-picker>`;
}
static styles = css`
ha-mount-picker {
width: 100%;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-backup-location": HaBackupLocationSelector;
}
}

View File

@ -33,6 +33,7 @@ const LOAD_ELEMENTS = {
object: () => import("./ha-selector-object"),
select: () => import("./ha-selector-select"),
state: () => import("./ha-selector-state"),
backup_location: () => import("./ha-selector-backup-location"),
stt: () => import("./ha-selector-stt"),
target: () => import("./ha-selector-target"),
template: () => import("./ha-selector-template"),

View File

@ -300,6 +300,11 @@ export interface StateSelector {
} | null;
}
export interface BackupLocationSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
backup_location: {} | null;
}
export interface StringSelector {
text: {
multiline?: boolean;

View File

@ -453,14 +453,16 @@
"addon-picker": {
"addon": "Add-on",
"error": {
"no_supervisor": {
"title": "No Supervisor",
"description": "Add-ons are not supported."
},
"fetch_addons": {
"title": "Error loading add-ons",
"description": "There was an error loading add-ons."
}
"no_supervisor": "Add-ons are not supported on your installation.",
"fetch_addons": "There was an error loading add-ons."
}
},
"mount-picker": {
"mount": "Location",
"use_datadisk": "Use data disk for backup",
"error": {
"no_supervisor": "Storage is not supported on your installation.",
"fetch_mounts": "There was an error loading the locations."
}
},
"stt-picker": {