22 mdiChartBox ,
33 mdiCog ,
44 mdiFolder ,
5+ mdiInformation ,
56 mdiPlayBoxMultiple ,
67 mdiPuzzle ,
78} from "@mdi/js" ;
@@ -11,17 +12,23 @@ import { customElement, property, state } from "lit/decorators";
1112import memoizeOne from "memoize-one" ;
1213import { isComponentLoaded } from "../../../../../common/config/is_component_loaded" ;
1314import { fireEvent } from "../../../../../common/dom/fire_event" ;
15+ import "../../../../../components/ha-alert" ;
1416import "../../../../../components/ha-button" ;
1517import "../../../../../components/ha-expansion-panel" ;
1618import "../../../../../components/ha-md-list" ;
1719import "../../../../../components/ha-md-list-item" ;
1820import "../../../../../components/ha-md-select" ;
1921import type { HaMdSelect } from "../../../../../components/ha-md-select" ;
2022import "../../../../../components/ha-md-select-option" ;
23+ import "../../../../../components/ha-spinner" ;
2124import "../../../../../components/ha-switch" ;
2225import type { HaSwitch } from "../../../../../components/ha-switch" ;
26+ import "../../../../../components/ha-tooltip" ;
2327import { fetchHassioAddonsInfo } from "../../../../../data/hassio/addon" ;
28+ import type { HostDisksUsage } from "../../../../../data/hassio/host" ;
29+ import { fetchHostDisksUsage } from "../../../../../data/hassio/host" ;
2430import type { HomeAssistant } from "../../../../../types" ;
31+ import { bytesToString } from "../../../../../util/bytes-to-string" ;
2532import "../ha-backup-addons-picker" ;
2633import type { BackupAddonItem } from "../ha-backup-addons-picker" ;
2734import { getRecorderInfo } from "../../../../../data/recorder" ;
@@ -78,11 +85,14 @@ class HaBackupConfigData extends LitElement {
7885
7986 @state ( ) private _showDbOption = true ;
8087
88+ @state ( ) private _storageInfo ?: HostDisksUsage | null ;
89+
8190 protected firstUpdated ( changedProperties : PropertyValues ) : void {
8291 super . firstUpdated ( changedProperties ) ;
8392 this . _checkDbOption ( ) ;
8493 if ( isComponentLoaded ( this . hass , "hassio" ) ) {
8594 this . _fetchAddons ( ) ;
95+ this . _fetchStorageInfo ( ) ;
8696 }
8797 }
8898
@@ -114,10 +124,68 @@ class HaBackupConfigData extends LitElement {
114124 }
115125 }
116126
127+ private async _fetchStorageInfo ( ) {
128+ try {
129+ this . _storageInfo = await fetchHostDisksUsage ( this . hass ) ;
130+ } catch ( _err : any ) {
131+ this . _storageInfo = null ;
132+ }
133+ }
134+
117135 private _hasLocalAddons ( addons : BackupAddonItem [ ] ) : boolean {
118136 return addons . some ( ( addon ) => addon . slug === "local" ) ;
119137 }
120138
139+ private _estimateBackupSize = memoizeOne (
140+ (
141+ data : FormData ,
142+ storageInfo : HostDisksUsage | null | undefined ,
143+ addonsLength : number
144+ ) : {
145+ compressedBytes : number ;
146+ addonsNotAccurate : boolean ;
147+ } | null => {
148+ if ( ! storageInfo ?. children ) {
149+ return null ;
150+ }
151+
152+ let totalBytes = 0 ;
153+
154+ const segments : Record < string , number > = { } ;
155+ storageInfo . children . forEach ( ( child ) => {
156+ segments [ child . id ] = child . used_bytes ;
157+ } ) ;
158+
159+ if ( data . homeassistant ) {
160+ totalBytes += segments . homeassistant ?? 0 ;
161+ }
162+ if ( data . media ) {
163+ totalBytes += segments . media ?? 0 ;
164+ }
165+ if ( data . share ) {
166+ totalBytes += segments . share ?? 0 ;
167+ }
168+
169+ if (
170+ data . addons_mode === "all" ||
171+ ( data . addons_mode === "custom" && data . addons . length > 0 )
172+ ) {
173+ // It would be better if we could receive individual addon sizes in the WS request instead
174+ totalBytes +=
175+ ( segments . addons_data ?? 0 ) + ( segments . addons_config ?? 0 ) ;
176+ }
177+
178+ return {
179+ // Estimate compressed size (40% reduction typical for gzip)
180+ compressedBytes : Math . round ( totalBytes * 0.6 ) ,
181+ addonsNotAccurate :
182+ data . addons_mode === "custom" &&
183+ data . addons . length > 0 &&
184+ data . addons . length !== addonsLength ,
185+ } ;
186+ }
187+ ) ;
188+
121189 private _getData = memoizeOne (
122190 ( value : BackupConfigData | undefined , showAddon : boolean ) : FormData => {
123191 if ( ! value ) {
@@ -171,6 +239,7 @@ class HaBackupConfigData extends LitElement {
171239 const isHassio = isComponentLoaded ( this . hass , "hassio" ) ;
172240
173241 return html `
242+ ${ this . _renderSizeEstimate ( ) }
174243 <ha- md- lis t>
175244 <ha- md- lis t- item>
176245 <ha- svg- icon slot= "start" .path = ${ mdiCog } > </ ha- svg- icon>
@@ -381,7 +450,103 @@ class HaBackupConfigData extends LitElement {
381450 } ) ;
382451 }
383452
453+ private _renderSizeEstimate ( ) {
454+ if ( ! isComponentLoaded ( this . hass , "hassio" ) ) {
455+ return nothing ;
456+ }
457+
458+ const data = this . _getData ( this . value , this . _showAddons ) ;
459+
460+ if (
461+ ! (
462+ data . homeassistant ||
463+ data . database ||
464+ data . media ||
465+ data . share ||
466+ data . local_addons ||
467+ data . addons_mode === "all" ||
468+ ( data . addons_mode === "custom" && data . addons . length > 0 )
469+ )
470+ ) {
471+ return nothing ;
472+ }
473+
474+ if ( this . _storageInfo === undefined ) {
475+ return html `
476+ <ha- alert alert- type= "info" >
477+ <ha- spinner slot= "icon" > </ ha- spinner>
478+ ${ this . hass . localize (
479+ "ui.panel.config.backup.data.estimated_size_loading"
480+ ) }
481+ </ ha- alert>
482+ ` ;
483+ }
484+
485+ if ( ! this . _storageInfo || ! this . _storageInfo . children ) {
486+ return nothing ;
487+ }
488+
489+ const result = this . _estimateBackupSize (
490+ data ,
491+ this . _storageInfo ,
492+ this . _addons . length
493+ ) ;
494+ if ( result === null ) {
495+ return nothing ;
496+ }
497+
498+ const { compressedBytes, addonsNotAccurate } = result ;
499+
500+ return html `
501+ <span class= "estimated-size" >
502+ <span class= "estimated-size-heading" >
503+ ${ this . hass . localize ( "ui.panel.config.backup.data.estimated_size" ) }
504+ <ha- svg- icon
505+ id= "estimated-size-info"
506+ .path = ${ mdiInformation }
507+ > </ ha- svg- icon>
508+ <ha- to oltip for = "estimated-size-info" placement = "right">
509+ ${ this . hass . localize (
510+ "ui.panel.config.backup.data.estimated_size_disclaimer"
511+ ) }
512+ ${ addonsNotAccurate
513+ ? html `<br / > <br / > ${ this . hass . localize (
514+ "ui.panel.config.backup.data.estimated_size_disclaimer_addons_custom"
515+ ) } `
516+ : nothing }
517+ </ ha- to oltip>
518+ </ span>
519+ <span class= "estimated-size-value" >
520+ ${ bytesToString ( compressedBytes ) }
521+ </ span>
522+ </ span>
523+ ` ;
524+ }
525+
384526 static styles = css `
527+ .estimated-size {
528+ display : block;
529+ margin-top : var (--ha-space-2 );
530+ font-size : var (--ha-font-size-m );
531+ }
532+ .estimated-size-heading {
533+ font-size : var (--ha-font-size-m );
534+ line-height : var (--ha-line-height-expanded );
535+ }
536+ .estimated-size-heading ha-svg-icon {
537+ --mdc-icon-size : 16px ;
538+ color : var (--secondary-text-color );
539+ margin-inline-start : var (--ha-space-1 );
540+ vertical-align : middle;
541+ }
542+ .estimated-size-value {
543+ display : block;
544+ font-size : var (--ha-font-size-s );
545+ color : var (--secondary-text-color );
546+ }
547+ ha-spinner {
548+ --ha-spinner-size : 24px ;
549+ }
385550 ha-md-list {
386551 background : none;
387552 --md-list-item-leading-space : 0 ;
0 commit comments