diff --git a/src/data/supervisor/update.ts b/src/data/supervisor/update.ts
new file mode 100644
index 0000000000..fc53b4399a
--- /dev/null
+++ b/src/data/supervisor/update.ts
@@ -0,0 +1,21 @@
+import type { HomeAssistant } from "../../types";
+
+export interface SupervisorUpdateConfig {
+ add_on_backup_before_update: boolean;
+ add_on_backup_retain_copies?: number;
+ core_backup_before_update: boolean;
+}
+
+export const getSupervisorUpdateConfig = async (hass: HomeAssistant) =>
+ hass.callWS({
+ type: "hassio/update/config/info",
+ });
+
+export const updateSupervisorUpdateConfig = async (
+ hass: HomeAssistant,
+ config: Partial
+) =>
+ hass.callWS({
+ type: "hassio/update/config/update",
+ ...config,
+ });
diff --git a/src/data/update.ts b/src/data/update.ts
index 29f7ecf054..e80fb2f285 100644
--- a/src/data/update.ts
+++ b/src/data/update.ts
@@ -207,7 +207,7 @@ export const computeUpdateStateDisplay = (
return hass.formatEntityState(stateObj);
};
-type UpdateType = "addon" | "home_assistant" | "generic";
+export type UpdateType = "addon" | "home_assistant" | "generic";
export const getUpdateType = (
stateObj: UpdateEntity,
diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts
index 206789552e..d4e0503604 100644
--- a/src/dialogs/more-info/controls/more-info-update.ts
+++ b/src/dialogs/more-info/controls/more-info-update.ts
@@ -1,26 +1,27 @@
import "@material/mwc-linear-progress/mwc-linear-progress";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
+import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { BINARY_STATE_OFF } from "../../../common/const";
import { relativeTime } from "../../../common/datetime/relative_time";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-checkbox";
-import "../../../components/ha-spinner";
import "../../../components/ha-faded";
import "../../../components/ha-formfield";
import "../../../components/ha-markdown";
import "../../../components/ha-md-list";
import "../../../components/ha-md-list-item";
+import "../../../components/ha-spinner";
import "../../../components/ha-switch";
-import type { HaSwitch } from "../../../components/ha-switch";
import type { BackupConfig } from "../../../data/backup";
import { fetchBackupConfig } from "../../../data/backup";
import { isUnavailableState } from "../../../data/entity";
import type { EntitySources } from "../../../data/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
-import type { UpdateEntity } from "../../../data/update";
+import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
+import type { UpdateEntity, UpdateType } from "../../../data/update";
import {
getUpdateType,
UpdateEntityFeature,
@@ -44,11 +45,38 @@ class MoreInfoUpdate extends LitElement {
@state() private _backupConfig?: BackupConfig;
+ @state() private _createBackup = false;
+
@state() private _entitySources?: EntitySources;
private async _fetchBackupConfig() {
- const { config } = await fetchBackupConfig(this.hass);
- this._backupConfig = config;
+ try {
+ const { config } = await fetchBackupConfig(this.hass);
+ this._backupConfig = config;
+ } catch (err) {
+ // ignore error, because user will get a manual backup option
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }
+ }
+
+ private async _fetchUpdateBackupConfig(type: UpdateType) {
+ try {
+ const config = await getSupervisorUpdateConfig(this.hass);
+
+ if (type === "home_assistant") {
+ this._createBackup = config.core_backup_before_update;
+ return;
+ }
+
+ if (type === "addon") {
+ this._createBackup = config.add_on_backup_before_update;
+ }
+ } catch (err) {
+ // ignore error, because user can still set the config
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }
}
private async _fetchEntitySources() {
@@ -256,7 +284,8 @@ class MoreInfoUpdate extends LitElement {
: nothing}
@@ -319,6 +348,12 @@ class MoreInfoUpdate extends LitElement {
if (supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
this._fetchEntitySources().then(() => {
const type = getUpdateType(this.stateObj!, this._entitySources!);
+ if (
+ isComponentLoaded(this.hass, "hassio") &&
+ ["home_assistant", "addon"].includes(type)
+ ) {
+ this._fetchUpdateBackupConfig(type);
+ }
if (type === "home_assistant") {
this._fetchBackupConfig();
}
@@ -347,13 +382,7 @@ class MoreInfoUpdate extends LitElement {
if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
return false;
}
- const createBackupSwitch = this.shadowRoot?.getElementById(
- "create-backup"
- ) as HaSwitch;
- if (createBackupSwitch) {
- return createBackupSwitch.checked;
- }
- return false;
+ return this._createBackup;
}
private _handleInstall(): void {
@@ -375,6 +404,10 @@ class MoreInfoUpdate extends LitElement {
this.hass.callService("update", "install", installData);
}
+ private _createBackupChanged(ev) {
+ this._createBackup = ev.target.checked;
+ }
+
private _handleSkip(): void {
if (this.stateObj!.attributes.auto_update) {
showAlertDialog(this, {
diff --git a/src/panels/config/backup/components/config/ha-backup-config-addon.ts b/src/panels/config/backup/components/config/ha-backup-config-addon.ts
new file mode 100644
index 0000000000..c9ba165579
--- /dev/null
+++ b/src/panels/config/backup/components/config/ha-backup-config-addon.ts
@@ -0,0 +1,132 @@
+import { css, html, LitElement } from "lit";
+import { customElement, property } from "lit/decorators";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+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-md-textfield";
+import type { HaMdTextfield } from "../../../../../components/ha-md-textfield";
+import type { SupervisorUpdateConfig } from "../../../../../data/supervisor/update";
+import type { HomeAssistant } from "../../../../../types";
+
+const MIN_RETENTION_VALUE = 1;
+
+@customElement("ha-backup-config-addon")
+class HaBackupConfigAddon extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false })
+ public supervisorUpdateConfig?: SupervisorUpdateConfig;
+
+ protected render() {
+ return html`
+
+
+
+ ${this.hass.localize(
+ `ui.panel.config.backup.schedule.update_preference.label`
+ )}
+
+
+ ${this.hass.localize(
+ `ui.panel.config.backup.schedule.update_preference.supporting_text`
+ )}
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.backup.schedule.update_preference.skip_backups"
+ )}
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.backup.schedule.update_preference.backup_before_update"
+ )}
+
+
+
+
+
+
+ ${this.hass.localize(`ui.panel.config.backup.schedule.retention`)}
+
+
+ ${this.hass.localize(
+ `ui.panel.config.backup.settings.addon_update_backup.retention_description`
+ )}
+
+
+
+
+
+ `;
+ }
+
+ private _updatePreferenceChanged(ev) {
+ ev.stopPropagation();
+ const target = ev.currentTarget as HaMdSelect;
+ const add_on_backup_before_update = target.value === "true";
+ fireEvent(this, "update-config-changed", {
+ value: {
+ add_on_backup_before_update,
+ },
+ });
+ }
+
+ private _backupRetentionChanged(ev) {
+ const target = ev.currentTarget as HaMdTextfield;
+ const add_on_backup_retain_copies = Number(target.value);
+ if (add_on_backup_retain_copies >= MIN_RETENTION_VALUE) {
+ fireEvent(this, "update-config-changed", {
+ value: {
+ add_on_backup_retain_copies,
+ },
+ });
+ }
+ }
+
+ static styles = css`
+ ha-md-list {
+ background: none;
+ --md-list-item-leading-space: 0;
+ --md-list-item-trailing-space: 0;
+ }
+ ha-md-list-item {
+ --md-item-overflow: visible;
+ }
+ ha-md-select {
+ min-width: 210px;
+ }
+ @media all and (max-width: 450px) {
+ ha-md-select {
+ min-width: 160px;
+ width: 160px;
+ --md-filled-field-content-space: 0;
+ }
+ }
+ `;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-backup-config-addon": HaBackupConfigAddon;
+ }
+}
diff --git a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts
index c19d334487..a0f7e9db2a 100644
--- a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts
+++ b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts
@@ -2,9 +2,13 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
+import { formatTime } from "../../../../../common/datetime/format_time";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { clamp } from "../../../../../common/number/clamp";
+import "../../../../../components/ha-checkbox";
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
+import "../../../../../components/ha-expansion-panel";
+import "../../../../../components/ha-formfield";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-md-select";
@@ -12,6 +16,8 @@ import type { HaMdSelect } from "../../../../../components/ha-md-select";
import "../../../../../components/ha-md-select-option";
import "../../../../../components/ha-md-textfield";
import "../../../../../components/ha-switch";
+import "../../../../../components/ha-time-input";
+import "../../../../../components/ha-tip";
import type { BackupConfig, BackupDay } from "../../../../../data/backup";
import {
BACKUP_DAYS,
@@ -20,13 +26,8 @@ import {
DEFAULT_OPTIMIZED_BACKUP_START_TIME,
sortWeekdays,
} from "../../../../../data/backup";
+import type { SupervisorUpdateConfig } from "../../../../../data/supervisor/update";
import type { HomeAssistant } from "../../../../../types";
-import "../../../../../components/ha-time-input";
-import "../../../../../components/ha-tip";
-import "../../../../../components/ha-expansion-panel";
-import "../../../../../components/ha-checkbox";
-import "../../../../../components/ha-formfield";
-import { formatTime } from "../../../../../common/datetime/format_time";
import { documentationUrl } from "../../../../../util/documentation-url";
export type BackupConfigSchedule = Pick;
@@ -116,6 +117,11 @@ class HaBackupConfigSchedule extends LitElement {
@property({ attribute: false }) public value?: BackupConfigSchedule;
+ @property({ type: Boolean }) public supervisor = false;
+
+ @property({ attribute: false })
+ public supervisorUpdateConfig?: SupervisorUpdateConfig;
+
@state() private _retentionPreset?: RetentionPreset;
protected willUpdate(changedProperties: PropertyValues): void {
@@ -333,6 +339,44 @@ class HaBackupConfigSchedule extends LitElement {
: nothing}
`
: nothing}
+ ${this.supervisor
+ ? html`
+
+
+ ${this.hass.localize(
+ `ui.panel.config.backup.schedule.update_preference.label`
+ )}
+
+
+ ${this.hass.localize(
+ `ui.panel.config.backup.schedule.update_preference.supporting_text`
+ )}
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.backup.schedule.update_preference.skip_backups"
+ )}
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.backup.schedule.update_preference.backup_before_update"
+ )}
+
+
+
+
+ `
+ : nothing}
+
${this.hass.localize(`ui.panel.config.backup.schedule.retention`)}
@@ -488,6 +532,17 @@ class HaBackupConfigSchedule extends LitElement {
});
}
+ private _updatePreferenceChanged(ev) {
+ ev.stopPropagation();
+ const target = ev.currentTarget as HaMdSelect;
+ const core_backup_before_update = target.value === "true";
+ fireEvent(this, "update-config-changed", {
+ value: {
+ core_backup_before_update,
+ },
+ });
+ }
+
private _retentionPresetChanged(ev) {
ev.stopPropagation();
const target = ev.currentTarget as HaMdSelect;
@@ -614,4 +669,10 @@ declare global {
interface HTMLElementTagNameMap {
"ha-backup-config-schedule": HaBackupConfigSchedule;
}
+
+ interface HASSDomEvents {
+ "update-config-changed": {
+ value: Partial;
+ };
+ }
}
diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts
index e841b872aa..42e39fdbad 100644
--- a/src/panels/config/backup/ha-config-backup-settings.ts
+++ b/src/panels/config/backup/ha-config-backup-settings.ts
@@ -7,20 +7,27 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import { debounce } from "../../../common/util/debounce";
import { nextRender } from "../../../common/util/render-status";
+import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next";
import "../../../components/ha-list-item";
-import "../../../components/ha-alert";
import "../../../components/ha-password-field";
import "../../../components/ha-svg-icon";
import type { BackupAgent, BackupConfig } from "../../../data/backup";
import { updateBackupConfig } from "../../../data/backup";
import type { CloudStatus } from "../../../data/cloud";
+import {
+ getSupervisorUpdateConfig,
+ updateSupervisorUpdateConfig,
+ type SupervisorUpdateConfig,
+} from "../../../data/supervisor/update";
import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
+import { documentationUrl } from "../../../util/documentation-url";
+import "./components/config/ha-backup-config-addon";
import "./components/config/ha-backup-config-agents";
import "./components/config/ha-backup-config-data";
import type { BackupConfigData } from "./components/config/ha-backup-config-data";
@@ -28,7 +35,6 @@ import "./components/config/ha-backup-config-encryption-key";
import "./components/config/ha-backup-config-schedule";
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location";
-import { documentationUrl } from "../../../util/documentation-url";
@customElement("ha-config-backup-settings")
class HaConfigBackupSettings extends LitElement {
@@ -44,11 +50,19 @@ class HaConfigBackupSettings extends LitElement {
@state() private _config?: BackupConfig;
+ @state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
+
+ @state() private _supervisorUpdateConfigError?: string;
+
protected willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);
if (changedProperties.has("config") && !this._config) {
this._config = this.config;
}
+
+ if (!this.hasUpdated && isComponentLoaded(this.hass, "hassio")) {
+ this._getSupervisorUpdateConfig();
+ }
}
public connectedCallback(): void {
@@ -58,6 +72,21 @@ class HaConfigBackupSettings extends LitElement {
this._config = this.config;
}
+ private async _getSupervisorUpdateConfig() {
+ try {
+ this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
+ } catch (err: any) {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ this._supervisorUpdateConfigError = this.hass.localize(
+ "ui.panel.config.backup.settings.addon_update_backup.error_load",
+ {
+ error: err?.message || err,
+ }
+ );
+ }
+ }
+
private async _scrollToSection() {
const hash = window.location.hash.substring(1);
if (
@@ -145,9 +174,17 @@ class HaConfigBackupSettings extends LitElement {
"ui.panel.config.backup.settings.schedule.description"
)}
+ ${this._supervisorUpdateConfigError
+ ? html`
+ ${this._supervisorUpdateConfigError}
+ `
+ : nothing}
@@ -230,6 +267,38 @@ class HaConfigBackupSettings extends LitElement {
: nothing}
+ ${supervisor
+ ? html`
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.backup.settings.addon_update_backup.description"
+ )}
+
+
+ ${this.hass.localize(
+ "ui.panel.config.backup.settings.addon_update_backup.local_only"
+ )}
+
+ ${this._supervisorUpdateConfigError
+ ? html`
+ ${this._supervisorUpdateConfigError}
+ `
+ : nothing}
+
+
+ `
+ : nothing}