Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions src/panels/config/backup/components/config/ha-backup-config-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
mdiChartBox,
mdiCog,
mdiFolder,
mdiInformation,
mdiPlayBoxMultiple,
mdiPuzzle,
} from "@mdi/js";
Expand All @@ -11,17 +12,23 @@ 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 "../../../../../components/ha-alert";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer used?

import "../../../../../components/ha-button";
import "../../../../../components/ha-expansion-panel";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-md-select";
import type { HaMdSelect } from "../../../../../components/ha-md-select";
import "../../../../../components/ha-md-select-option";
import "../../../../../components/ha-spinner";
import "../../../../../components/ha-switch";
import type { HaSwitch } from "../../../../../components/ha-switch";
import "../../../../../components/ha-tooltip";
import { fetchHassioAddonsInfo } from "../../../../../data/hassio/addon";
import type { HostDisksUsage } from "../../../../../data/hassio/host";
import { fetchHostDisksUsage } from "../../../../../data/hassio/host";
import type { HomeAssistant } from "../../../../../types";
import { bytesToString } from "../../../../../util/bytes-to-string";
import "../ha-backup-addons-picker";
import type { BackupAddonItem } from "../ha-backup-addons-picker";
import { getRecorderInfo } from "../../../../../data/recorder";
Expand Down Expand Up @@ -78,11 +85,14 @@ class HaBackupConfigData extends LitElement {

@state() private _showDbOption = true;

@state() private _storageInfo?: HostDisksUsage | null;

protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
this._checkDbOption();
if (isComponentLoaded(this.hass, "hassio")) {
this._fetchAddons();
this._fetchStorageInfo();
}
}

Expand Down Expand Up @@ -114,10 +124,68 @@ class HaBackupConfigData extends LitElement {
}
}

private async _fetchStorageInfo() {
try {
this._storageInfo = await fetchHostDisksUsage(this.hass);
} catch (_err: any) {
this._storageInfo = null;
}
}

private _hasLocalAddons(addons: BackupAddonItem[]): boolean {
return addons.some((addon) => addon.slug === "local");
}

private _estimateBackupSize = memoizeOne(
(
data: FormData,
storageInfo: HostDisksUsage | null | undefined,
addonsLength: number
): {
compressedBytes: number;
addonsNotAccurate: boolean;
} | null => {
if (!storageInfo?.children) {
return null;
}

let totalBytes = 0;

const segments: Record<string, number> = {};
storageInfo.children.forEach((child) => {
segments[child.id] = child.used_bytes;
});

if (data.homeassistant) {
totalBytes += segments.homeassistant ?? 0;
}
if (data.media) {
totalBytes += segments.media ?? 0;
}
if (data.share) {
totalBytes += segments.share ?? 0;
}

if (
data.addons_mode === "all" ||
(data.addons_mode === "custom" && data.addons.length > 0)
) {
// It would be better if we could receive individual addon sizes in the WS request instead
totalBytes +=
(segments.addons_data ?? 0) + (segments.addons_config ?? 0);
}

return {
// Estimate compressed size (40% reduction typical for gzip)
compressedBytes: Math.round(totalBytes * 0.6),
addonsNotAccurate:
data.addons_mode === "custom" &&
data.addons.length > 0 &&
data.addons.length !== addonsLength,
};
}
);

private _getData = memoizeOne(
(value: BackupConfigData | undefined, showAddon: boolean): FormData => {
if (!value) {
Expand Down Expand Up @@ -171,6 +239,7 @@ class HaBackupConfigData extends LitElement {
const isHassio = isComponentLoaded(this.hass, "hassio");

return html`
${this._renderSizeEstimate()}
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
Expand Down Expand Up @@ -381,7 +450,103 @@ class HaBackupConfigData extends LitElement {
});
}

private _renderSizeEstimate() {
if (!isComponentLoaded(this.hass, "hassio")) {
return nothing;
}

const data = this._getData(this.value, this._showAddons);

if (
!(
data.homeassistant ||
data.database ||
data.media ||
data.share ||
data.local_addons ||
data.addons_mode === "all" ||
(data.addons_mode === "custom" && data.addons.length > 0)
)
) {
return nothing;
}

if (this._storageInfo === undefined) {
return html`
<ha-alert alert-type="info">
<ha-spinner slot="icon"></ha-spinner>
${this.hass.localize(
"ui.panel.config.backup.data.estimated_size_loading"
)}
</ha-alert>
`;
}

if (!this._storageInfo || !this._storageInfo.children) {
return nothing;
}

const result = this._estimateBackupSize(
data,
this._storageInfo,
this._addons.length
);
if (result === null) {
return nothing;
}

const { compressedBytes, addonsNotAccurate } = result;

return html`
<span class="estimated-size">
<span class="estimated-size-heading">
${this.hass.localize("ui.panel.config.backup.data.estimated_size")}
<ha-svg-icon
id="estimated-size-info"
.path=${mdiInformation}
></ha-svg-icon>
<ha-tooltip for="estimated-size-info" placement="right">
${this.hass.localize(
"ui.panel.config.backup.data.estimated_size_disclaimer"
)}
${addonsNotAccurate
? html`<br /><br />${this.hass.localize(
"ui.panel.config.backup.data.estimated_size_disclaimer_addons_custom"
)}`
: nothing}
</ha-tooltip>
</span>
<span class="estimated-size-value">
${bytesToString(compressedBytes)}
</span>
</span>
`;
}

static styles = css`
.estimated-size {
display: block;
margin-top: var(--ha-space-2);
font-size: var(--ha-font-size-m);
}
.estimated-size-heading {
font-size: var(--ha-font-size-m);
line-height: var(--ha-line-height-expanded);
}
.estimated-size-heading ha-svg-icon {
--mdc-icon-size: 16px;
color: var(--secondary-text-color);
margin-inline-start: var(--ha-space-1);
vertical-align: middle;
}
.estimated-size-value {
display: block;
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
}
ha-spinner {
--ha-spinner-size: 24px;
}
ha-md-list {
background: none;
--md-list-item-leading-space: 0;
Expand Down
6 changes: 5 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2674,7 +2674,11 @@
"addons_description": "Select what add-ons you want to include.",
"addons_all": "All",
"addons_none": "None",
"addons_custom": "Custom"
"addons_custom": "Custom",
"estimated_size": "Estimated backup size",
"estimated_size_disclaimer": "This is an approximation based on storage usage and compression. The actual backup size will vary.",
"estimated_size_disclaimer_addons_custom": "This estimate includes all add-ons, and not individual add-on sizes.",
"estimated_size_loading": "Loading size estimate..."
},
"data_picker": {
"settings": "Settings",
Expand Down
Loading