mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 00:36:34 +00:00
Add backup location selector (#16567)
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
parent
22dc757382
commit
a5b5e61ed4
@ -5,13 +5,15 @@ import { isComponentLoaded } from "../common/config/is_component_loaded";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon";
|
import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { HomeAssistant, ValueChangedEvent } from "../types";
|
||||||
import { ValueChangedEvent, HomeAssistant } from "../types";
|
import "./ha-alert";
|
||||||
import { HaComboBox } from "./ha-combo-box";
|
import "./ha-combo-box";
|
||||||
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
|
import "./ha-list-item";
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
||||||
item
|
item
|
||||||
) => html`<mwc-list-item twoline graphic="icon">
|
) => html`<ha-list-item twoline graphic="icon">
|
||||||
<span>${item.name}</span>
|
<span>${item.name}</span>
|
||||||
<span slot="secondary">${item.slug}</span>
|
<span slot="secondary">${item.slug}</span>
|
||||||
${item.icon
|
${item.icon
|
||||||
@ -21,7 +23,7 @@ const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
|||||||
.src="/api/hassio/addons/${item.slug}/icon"
|
.src="/api/hassio/addons/${item.slug}/icon"
|
||||||
/>`
|
/>`
|
||||||
: ""}
|
: ""}
|
||||||
</mwc-list-item>`;
|
</ha-list-item>`;
|
||||||
|
|
||||||
@customElement("ha-addon-picker")
|
@customElement("ha-addon-picker")
|
||||||
class HaAddonPicker extends LitElement {
|
class HaAddonPicker extends LitElement {
|
||||||
@ -41,6 +43,8 @@ class HaAddonPicker extends LitElement {
|
|||||||
|
|
||||||
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this._comboBox?.open();
|
this._comboBox?.open();
|
||||||
}
|
}
|
||||||
@ -57,6 +61,9 @@ class HaAddonPicker extends LitElement {
|
|||||||
if (!this._addons) {
|
if (!this._addons) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
if (this._error) {
|
||||||
|
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-combo-box
|
<ha-combo-box
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -87,24 +94,14 @@ class HaAddonPicker extends LitElement {
|
|||||||
stringCompare(a.name, b.name, this.hass.locale.language)
|
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
showAlertDialog(this, {
|
this._error = this.hass.localize(
|
||||||
title: this.hass.localize(
|
"ui.components.addon-picker.error.no_supervisor"
|
||||||
"ui.components.addon-picker.error.no_supervisor.title"
|
);
|
||||||
),
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.components.addon-picker.error.no_supervisor.description"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
this._error = this.hass.localize(
|
||||||
title: this.hass.localize(
|
"ui.components.addon-picker.error.fetch_addons"
|
||||||
"ui.components.addon-picker.error.fetch_addons.title"
|
);
|
||||||
),
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.components.addon-picker.error.fetch_addons.description"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
179
src/components/ha-mount-picker.ts
Normal file
179
src/components/ha-mount-picker.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
46
src/components/ha-selector/ha-selector-backup-location.ts
Normal file
46
src/components/ha-selector/ha-selector-backup-location.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ const LOAD_ELEMENTS = {
|
|||||||
object: () => import("./ha-selector-object"),
|
object: () => import("./ha-selector-object"),
|
||||||
select: () => import("./ha-selector-select"),
|
select: () => import("./ha-selector-select"),
|
||||||
state: () => import("./ha-selector-state"),
|
state: () => import("./ha-selector-state"),
|
||||||
|
backup_location: () => import("./ha-selector-backup-location"),
|
||||||
stt: () => import("./ha-selector-stt"),
|
stt: () => import("./ha-selector-stt"),
|
||||||
target: () => import("./ha-selector-target"),
|
target: () => import("./ha-selector-target"),
|
||||||
template: () => import("./ha-selector-template"),
|
template: () => import("./ha-selector-template"),
|
||||||
|
@ -300,6 +300,11 @@ export interface StateSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BackupLocationSelector {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
backup_location: {} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StringSelector {
|
export interface StringSelector {
|
||||||
text: {
|
text: {
|
||||||
multiline?: boolean;
|
multiline?: boolean;
|
||||||
|
@ -453,14 +453,16 @@
|
|||||||
"addon-picker": {
|
"addon-picker": {
|
||||||
"addon": "Add-on",
|
"addon": "Add-on",
|
||||||
"error": {
|
"error": {
|
||||||
"no_supervisor": {
|
"no_supervisor": "Add-ons are not supported on your installation.",
|
||||||
"title": "No Supervisor",
|
"fetch_addons": "There was an error loading add-ons."
|
||||||
"description": "Add-ons are not supported."
|
}
|
||||||
},
|
},
|
||||||
"fetch_addons": {
|
"mount-picker": {
|
||||||
"title": "Error loading add-ons",
|
"mount": "Location",
|
||||||
"description": "There was an error loading add-ons."
|
"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": {
|
"stt-picker": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user