Add guards for network storage (#16591)

This commit is contained in:
Joakim Sørensen 2023-05-23 13:33:12 +03:00 committed by GitHub
parent 4ccfd6a3fc
commit 22dc757382
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 113 additions and 30 deletions

View File

@ -17,6 +17,10 @@ export enum SupervisorMountState {
UNKNOWN = "unknown", UNKNOWN = "unknown",
} }
interface MountOptions {
default_backup_mount?: string | null;
}
interface SupervisorMountBase { interface SupervisorMountBase {
name: string; name: string;
usage: SupervisorMountUsage; usage: SupervisorMountUsage;
@ -53,6 +57,7 @@ export type SupervisorMountRequestParams =
| SupervisorCIFSMountRequestParams; | SupervisorCIFSMountRequestParams;
export interface SupervisorMounts { export interface SupervisorMounts {
default_backup_mount: string | null;
mounts: SupervisorMount[]; mounts: SupervisorMount[];
} }
@ -111,3 +116,15 @@ export const reloadSupervisorMount = async (
method: "post", method: "post",
timeout: null, timeout: null,
}); });
export const changeMountOptions = async (
hass: HomeAssistant,
data: MountOptions
): Promise<void> =>
hass.callWS({
type: "supervisor/api",
endpoint: `/mounts/options`,
method: "post",
timeout: null,
data,
});

View File

@ -15,6 +15,7 @@ import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-metric"; import "../../../components/ha-metric";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import "../../../components/ha-icon-next";
import { extractApiErrorMessage } from "../../../data/hassio/common"; import { extractApiErrorMessage } from "../../../data/hassio/common";
import { HassioHostInfo, fetchHassioHostInfo } from "../../../data/hassio/host"; import { HassioHostInfo, fetchHassioHostInfo } from "../../../data/hassio/host";
import { import {
@ -22,6 +23,7 @@ import {
SupervisorMountState, SupervisorMountState,
SupervisorMountType, SupervisorMountType,
SupervisorMountUsage, SupervisorMountUsage,
SupervisorMounts,
fetchSupervisorMounts, fetchSupervisorMounts,
reloadSupervisorMount, reloadSupervisorMount,
} from "../../../data/supervisor/mounts"; } from "../../../data/supervisor/mounts";
@ -35,6 +37,7 @@ import {
import "../core/ha-config-analytics"; import "../core/ha-config-analytics";
import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk"; import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk";
import { showMountViewDialog } from "./show-dialog-view-mount"; import { showMountViewDialog } from "./show-dialog-view-mount";
import { navigate } from "../../../common/navigate";
@customElement("ha-config-section-storage") @customElement("ha-config-section-storage")
class HaConfigSectionStorage extends LitElement { class HaConfigSectionStorage extends LitElement {
@ -48,7 +51,7 @@ class HaConfigSectionStorage extends LitElement {
@state() private _hostInfo?: HassioHostInfo; @state() private _hostInfo?: HassioHostInfo;
@state() private _mounts?: SupervisorMount[]; @state() private _mountsInfo?: SupervisorMounts | null;
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
@ -57,7 +60,14 @@ class HaConfigSectionStorage extends LitElement {
} }
} }
protected render(): TemplateResult { protected render(): TemplateResult | typeof nothing {
if (this._mountsInfo === undefined) {
return nothing;
}
const validMounts = this._mountsInfo?.mounts.filter((mount) =>
[SupervisorMountType.CIFS, SupervisorMountType.NFS].includes(mount.type)
);
const isHAOS = this._hostInfo?.features.includes("haos");
return html` return html`
<hass-subpage <hass-subpage
back-path="/config/system" back-path="/config/system"
@ -121,21 +131,46 @@ class HaConfigSectionStorage extends LitElement {
</ha-card> </ha-card>
` `
: ""} : ""}
<ha-card <ha-card
outlined outlined
.header=${this.hass.localize( .header=${this.hass.localize(
"ui.panel.config.storage.network_mounts.title" "ui.panel.config.storage.network_mounts.title"
)} )}
> >
${this._mounts?.length ${this._mountsInfo === null
? html`<ha-alert
class="mounts-not-supported"
alert-type="warning"
.title=${this.hass.localize(
"ui.panel.config.storage.network_mounts.not_supported.title"
)}
>
${isHAOS
? html`${this.hass.localize(
"ui.panel.config.storage.network_mounts.not_supported.haos"
)}
<mwc-button
slot="action"
@click=${this._navigateToUpdates}
>
${this.hass.localize(
"ui.panel.config.storage.network_mounts.not_supported.navigate_to_updates"
)}
</mwc-button>`
: this.hass.localize(
"ui.panel.config.storage.network_mounts.not_supported.supervised"
)}
</ha-alert>`
: validMounts?.length
? html`<mwc-list> ? html`<mwc-list>
${this._mounts.map( ${validMounts.map(
(mount) => html` (mount) => html`
<ha-list-item <ha-list-item
graphic="avatar" graphic="avatar"
.mount=${mount} .mount=${mount}
twoline twoline
.hasMeta=${mount.state !== SupervisorMountState.ACTIVE} hasMeta
@click=${this._changeMount} @click=${this._changeMount}
> >
<div slot="graphic"> <div slot="graphic">
@ -155,18 +190,20 @@ class HaConfigSectionStorage extends LitElement {
? mount.path ? mount.path
: ` :${mount.share}`} : ` :${mount.share}`}
</span> </span>
<ha-icon-button ${mount.state !== SupervisorMountState.ACTIVE
class="reload-btn" ? html`<ha-icon-button
slot="meta" class="reload-btn"
.mount=${mount} slot="meta"
@click=${this._reloadMount} .mount=${mount}
.path=${mdiReload} @click=${this._reloadMount}
></ha-icon-button> .path=${mdiReload}
></ha-icon-button>`
: html`<ha-icon-next slot="meta"></ha-icon-next>`}
</ha-list-item> </ha-list-item>
` `
)} )}
</mwc-list>` </mwc-list>`
: html` <div class="no-mounts"> : html`<div class="no-mounts">
<ha-svg-icon .path=${mdiNas}></ha-svg-icon> <ha-svg-icon .path=${mdiNas}></ha-svg-icon>
<p> <p>
${this.hass.localize( ${this.hass.localize(
@ -174,14 +211,15 @@ class HaConfigSectionStorage extends LitElement {
)} )}
</p> </p>
</div>`} </div>`}
${this._mountsInfo !== null
<div class="card-actions"> ? html`<div class="card-actions">
<mwc-button @click=${this._addMount}> <mwc-button @click=${this._addMount}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.storage.network_mounts.add_title" "ui.panel.config.storage.network_mounts.add_title"
)} )}
</mwc-button> </mwc-button>
</div> </div>`
: nothing}
</ha-card> </ha-card>
</div> </div>
</hass-subpage> </hass-subpage>
@ -190,13 +228,15 @@ class HaConfigSectionStorage extends LitElement {
private async _load() { private async _load() {
try { try {
[this._hostInfo] = await Promise.all([ this._hostInfo = await fetchHassioHostInfo(this.hass);
fetchHassioHostInfo(this.hass),
this._reloadMounts(),
]);
} catch (err: any) { } catch (err: any) {
this._error = err.message || err; this._error = err.message || err;
} }
if (this._hostInfo?.features.includes("mount")) {
await this._reloadMounts();
} else {
this._mountsInfo = null;
}
} }
private _moveDatadisk(): void { private _moveDatadisk(): void {
@ -205,6 +245,10 @@ class HaConfigSectionStorage extends LitElement {
}); });
} }
private async _navigateToUpdates(): Promise<void> {
navigate("/config/updates");
}
private async _reloadMount(ev: Event): Promise<void> { private async _reloadMount(ev: Event): Promise<void> {
ev.stopPropagation(); ev.stopPropagation();
const mount: SupervisorMount = (ev.currentTarget as any).mount; const mount: SupervisorMount = (ev.currentTarget as any).mount;
@ -224,7 +268,9 @@ class HaConfigSectionStorage extends LitElement {
} }
private _addMount(): void { private _addMount(): void {
showMountViewDialog(this, { reloadMounts: this._reloadMounts }); showMountViewDialog(this, {
reloadMounts: this._reloadMounts,
});
} }
private _changeMount(ev: Event): void { private _changeMount(ev: Event): void {
@ -237,12 +283,10 @@ class HaConfigSectionStorage extends LitElement {
private async _reloadMounts(): Promise<void> { private async _reloadMounts(): Promise<void> {
try { try {
const allMounts = await fetchSupervisorMounts(this.hass); this._mountsInfo = await fetchSupervisorMounts(this.hass);
this._mounts = allMounts.mounts.filter((mount) =>
[SupervisorMountType.CIFS, SupervisorMountType.NFS].includes(mount.type)
);
} catch (err: any) { } catch (err: any) {
this._error = err.message || err; this._error = err.message || err;
this._mountsInfo = null;
} }
} }
@ -274,6 +318,10 @@ class HaConfigSectionStorage extends LitElement {
color: var(--warning-color); color: var(--warning-color);
} }
.mounts-not-supported {
padding: 0 16px 16px;
}
.reload-btn { .reload-btn {
float: right; float: right;
position: relative; position: relative;
@ -295,6 +343,14 @@ class HaConfigSectionStorage extends LitElement {
border-radius: 50%; border-radius: 50%;
margin-bottom: 8px; margin-bottom: 8px;
} }
ha-list-item {
--mdc-list-item-meta-size: auto;
--mdc-list-item-meta-display: flex;
}
ha-svg-icon,
ha-icon-next {
width: 24px;
}
`; `;
} }

View File

@ -3987,6 +3987,12 @@
"add_title": "Add network storage", "add_title": "Add network storage",
"update_title": "Update network storage", "update_title": "Update network storage",
"no_mounts": "No connected network storage", "no_mounts": "No connected network storage",
"not_supported": {
"title": "The operating system does not support network storage",
"supervised": "Network storage is not supported on this host",
"haos": "To use network storage you need to run at least Home Assistant Operating System 10.0",
"navigate_to_updates": "Go to updates"
},
"mount_usage": { "mount_usage": {
"backup": "Backup", "backup": "Backup",
"media": "Media" "media": "Media"
@ -4008,6 +4014,10 @@
"title": "Server", "title": "Server",
"description": "This is the domain name (FQDN) or IP address of the storage server you want to connect to" "description": "This is the domain name (FQDN) or IP address of the storage server you want to connect to"
}, },
"default_backup_mount": {
"title": "Default backup location",
"description": "This will be the default location for backups"
},
"path": { "path": {
"title": "Remote share path", "title": "Remote share path",
"description": "This is the path of the remote share on your storage server" "description": "This is the path of the remote share on your storage server"