diff --git a/src/panels/config/backup/components/ha-backup-agents-select.ts b/src/panels/config/backup/components/ha-backup-agents-select.ts
new file mode 100644
index 0000000000..120c4fb34b
--- /dev/null
+++ b/src/panels/config/backup/components/ha-backup-agents-select.ts
@@ -0,0 +1,104 @@
+import { css, html, LitElement } from "lit";
+import { customElement, property } from "lit/decorators";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import "../../../../components/ha-checkbox";
+import "../../../../components/ha-formfield";
+import type { BackupAgent } from "../../../../data/backup";
+import type { HomeAssistant } from "../../../../types";
+import { brandsUrl } from "../../../../util/brands-url";
+import { domainToName } from "../../../../data/integration";
+
+@customElement("ha-backup-agents-select")
+class HaBackupAgentsSelect extends LitElement {
+ @property({ attribute: false })
+ public hass!: HomeAssistant;
+
+ @property({ type: Boolean })
+ public disabled = false;
+
+ @property({ attribute: false })
+ public agents!: BackupAgent[];
+
+ @property({ attribute: false })
+ public disabledAgents?: string[];
+
+ @property({ attribute: false })
+ public value!: string[];
+
+ render() {
+ return html`
+
+ ${this.agents.map((agent) => this._renderAgent(agent))}
+
+ `;
+ }
+
+ private _renderAgent(agent: BackupAgent) {
+ const [domain, name] = agent.agent_id.split(".");
+ const domainName = domainToName(this.hass.localize, domain);
+ return html`
+
+
+
+ ${domainName}: ${name}
+
+
+ `;
+ }
+
+ private _checkboxChanged(ev: Event) {
+ const checkbox = ev.target as HTMLInputElement;
+ const value = checkbox.value;
+ const index = this.value.indexOf(value);
+ if (checkbox.checked && index === -1) {
+ this.value = [...this.value, value];
+ } else if (!checkbox.checked && index !== -1) {
+ this.value = [
+ ...this.value.slice(0, index),
+ ...this.value.slice(index + 1),
+ ];
+ }
+ fireEvent(this, "value-changed", { value: this.value });
+ }
+
+ static styles = css`
+ img {
+ height: 24px;
+ width: 24px;
+ }
+ .agents {
+ display: flex;
+ flex-direction: column;
+ }
+ .label {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 16px;
+ }
+ `;
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-backup-agents-select": HaBackupAgentsSelect;
+ }
+}
diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts
new file mode 100644
index 0000000000..97e011e104
--- /dev/null
+++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts
@@ -0,0 +1,372 @@
+import {
+ mdiChartBox,
+ mdiClose,
+ mdiCog,
+ mdiFolder,
+ mdiPlayBoxMultiple,
+} from "@mdi/js";
+import type { CSSResultGroup } from "lit";
+import { LitElement, css, html, nothing } from "lit";
+import { customElement, property, query, state } from "lit/decorators";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import "../../../../components/ha-button";
+import "../../../../components/ha-dialog-header";
+import "../../../../components/ha-expansion-panel";
+import "../../../../components/ha-icon-button";
+import "../../../../components/ha-icon-button-prev";
+import "../../../../components/ha-md-dialog";
+import type { HaMdDialog } from "../../../../components/ha-md-dialog";
+import "../../../../components/ha-md-select";
+import "../../../../components/ha-md-select-option";
+import "../../../../components/ha-settings-row";
+import "../../../../components/ha-svg-icon";
+import "../../../../components/ha-switch";
+import "../../../../components/ha-textfield";
+import type { BackupAgent } from "../../../../data/backup";
+import { fetchBackupAgentsInfo, generateBackup } from "../../../../data/backup";
+import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
+import { haStyle, haStyleDialog } from "../../../../resources/styles";
+import type { HomeAssistant } from "../../../../types";
+import "../components/ha-backup-agents-select";
+import type { GenerateBackupDialogParams } from "./show-dialog-generate-backup";
+
+type FormData = {
+ name: string;
+ history: boolean;
+ media: boolean;
+ share: boolean;
+ addons_mode: "all" | "custom";
+ addons: string[];
+ agents_mode: "all" | "custom";
+ agents: string[];
+};
+
+const INITIAL_FORM_DATA: FormData = {
+ name: "",
+ history: true,
+ media: false,
+ share: false,
+ addons_mode: "all",
+ addons: [],
+ agents_mode: "all",
+ agents: [],
+};
+
+const STEPS = ["data", "sync"] as const;
+
+@customElement("ha-dialog-generate-backup")
+class DialogGenerateBackup extends LitElement implements HassDialog {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @state() private _formData?: FormData;
+
+ @state() private _step?: "data" | "sync";
+
+ @state() private _agents: BackupAgent[] = [];
+
+ @state() private _params?: GenerateBackupDialogParams;
+
+ @query("ha-md-dialog") private _dialog?: HaMdDialog;
+
+ public showDialog(_params: GenerateBackupDialogParams): void {
+ this._step = STEPS[0];
+ this._formData = INITIAL_FORM_DATA;
+ this._params = _params;
+ this._fetchAgents();
+ }
+
+ private _dialogClosed() {
+ this._step = undefined;
+ this._formData = undefined;
+ this._agents = [];
+ this._params = undefined;
+ fireEvent(this, "dialog-closed", { dialog: this.localName });
+ }
+
+ private async _fetchAgents() {
+ const { agents } = await fetchBackupAgentsInfo(this.hass);
+ this._agents = agents;
+ }
+
+ public closeDialog() {
+ this._dialog?.close();
+ }
+
+ private _previousStep() {
+ const index = STEPS.indexOf(this._step!);
+ if (index === 0) {
+ return;
+ }
+ this._step = STEPS[index - 1];
+ }
+
+ private _nextStep() {
+ const index = STEPS.indexOf(this._step!);
+ if (index === STEPS.length - 1) {
+ return;
+ }
+ this._step = STEPS[index + 1];
+ }
+
+ protected render() {
+ if (!this._step || !this._formData) {
+ return nothing;
+ }
+
+ const dialogTitle =
+ this._step === "sync" ? "Synchronization" : "Backup data";
+
+ const isFirstStep = this._step === STEPS[0];
+ const isLastStep = this._step === STEPS[STEPS.length - 1];
+
+ return html`
+
+
+ ${isFirstStep
+ ? html`
+
+ `
+ : html`
+
+ `}
+ ${dialogTitle}
+
+
+ ${this._step === "data" ? this._renderData() : this._renderSync()}
+
+
+ ${isFirstStep
+ ? html`Cancel`
+ : nothing}
+ ${isLastStep
+ ? html`Create backup`
+ : html`Next`}
+
+
+ `;
+ }
+
+ private _renderData() {
+ if (!this._formData) {
+ return nothing;
+ }
+ return html`
+
+
+ Home Assistant settings
+
+ With these settings you are able to restore your system.
+
+
+
+
+
+ History
+ For example of your energy dashboard.
+
+
+
+
+ Media
+
+ Folder that is often used for advanced or older configurations.
+
+
+
+
+
+ Share folder
+
+ Folder that is often used for advanced or older configurations.
+
+
+
+ `;
+ }
+
+ private _renderSync() {
+ if (!this._formData) {
+ return nothing;
+ }
+ return html`
+
+
+
+ Locations
+
+ What locations you want to automatically backup to.
+
+
+
+ All (${this._agents.length})
+
+
+ Custom
+
+
+
+ ${this._formData.agents_mode === "custom"
+ ? html`
+
+
+
+ `
+ : nothing}
+ `;
+ }
+
+ private _agentModeChanged(ev) {
+ const select = ev.currentTarget;
+ this._formData = {
+ ...this._formData!,
+ agents_mode: select.value,
+ };
+ }
+
+ private _agentsChanged(ev) {
+ this._formData = {
+ ...this._formData!,
+ agents: ev.detail.value,
+ };
+ }
+
+ private _switchChanged(ev) {
+ const _switch = ev.currentTarget;
+ this._formData = {
+ ...this._formData!,
+ [_switch.id]: _switch.checked,
+ };
+ }
+
+ private _nameChanged(ev) {
+ this._formData = {
+ ...this._formData!,
+ name: ev.target.value,
+ };
+ }
+
+ private async _submit() {
+ if (!this._formData) {
+ return;
+ }
+
+ const {
+ addons,
+ addons_mode,
+ agents,
+ agents_mode,
+ history,
+ media,
+ name,
+ share,
+ } = this._formData;
+
+ const folders: string[] = [];
+ if (media) {
+ folders.push("media");
+ }
+ if (share) {
+ folders.push("share");
+ }
+
+ // TODO: Fetch all addons
+ const ALL_ADDONS = [];
+ const { slug } = await generateBackup(this.hass, {
+ name,
+ agent_ids:
+ agents_mode === "all"
+ ? this._agents.map((agent) => agent.agent_id)
+ : agents,
+ database_included: history,
+ folders_included: folders,
+ addons_included: addons_mode === "all" ? ALL_ADDONS : addons,
+ });
+
+ this._params!.submit?.({ slug });
+ this.closeDialog();
+ }
+
+ static get styles(): CSSResultGroup {
+ return [
+ haStyle,
+ haStyleDialog,
+ css`
+ :host {
+ --dialog-content-overflow: visible;
+ }
+ ha-md-dialog {
+ --dialog-content-padding: 24px;
+ }
+ ha-settings-row {
+ --settings-row-prefix-display: flex;
+ padding: 0;
+ }
+ ha-settings-row > ha-svg-icon {
+ align-self: center;
+ margin-inline-end: 16px;
+ }
+ ha-settings-row > ha-md-select {
+ min-width: 150px;
+ }
+ ha-settings-row > ha-md-select > span {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+ ha-settings-row > ha-md-select-option {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ ha-textfield {
+ width: 100%;
+ }
+ .content {
+ padding-top: 0;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-dialog-generate-backup": DialogGenerateBackup;
+ }
+}
diff --git a/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts b/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts
new file mode 100644
index 0000000000..79bd55a3b3
--- /dev/null
+++ b/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts
@@ -0,0 +1,37 @@
+import { fireEvent } from "../../../../common/dom/fire_event";
+
+export interface GenerateBackupDialogParams {
+ submit?: (response: { slug: string }) => void;
+ cancel?: () => void;
+}
+
+export const loadGenerateBackupDialog = () =>
+ import("./dialog-generate-backup");
+
+export const showGenerateBackupDialog = (
+ element: HTMLElement,
+ params: GenerateBackupDialogParams
+) =>
+ new Promise<{ slug: string } | null>((resolve) => {
+ const origCancel = params.cancel;
+ const origSubmit = params.submit;
+ fireEvent(element, "show-dialog", {
+ dialogTag: "ha-dialog-generate-backup",
+ dialogImport: loadGenerateBackupDialog,
+ dialogParams: {
+ ...params,
+ cancel: () => {
+ resolve(null);
+ if (origCancel) {
+ origCancel();
+ }
+ },
+ submit: (response) => {
+ resolve(response);
+ if (origSubmit) {
+ origSubmit(response);
+ }
+ },
+ },
+ });
+ });
diff --git a/src/panels/config/backup/ha-config-backup-dashboard.ts b/src/panels/config/backup/ha-config-backup-dashboard.ts
index 0bf26fa3a9..5b6ae60a17 100644
--- a/src/panels/config/backup/ha-config-backup-dashboard.ts
+++ b/src/panels/config/backup/ha-config-backup-dashboard.ts
@@ -22,22 +22,22 @@ import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon";
import {
fetchBackupInfo,
- generateBackup,
removeBackup,
type BackupContent,
} from "../../../data/backup";
import { extractApiErrorMessage } from "../../../data/hassio/common";
+import {
+ showAlertDialog,
+ showConfirmationDialog,
+} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
-import {
- showAlertDialog,
- showConfirmationDialog,
-} from "../../lovelace/custom-card-helpers";
import "./components/ha-backup-summary-card";
+import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
@customElement("ha-config-backup-dashboard")
class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
@@ -244,21 +244,10 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
}
private async _generateBackup(): Promise {
- const confirm = await showConfirmationDialog(this, {
- title: this.hass.localize("ui.panel.config.backup.create.title"),
- text: this.hass.localize("ui.panel.config.backup.create.description"),
- confirmText: this.hass.localize("ui.panel.config.backup.create.confirm"),
- });
- if (!confirm) {
- return;
- }
+ const response = await showGenerateBackupDialog(this, {});
- try {
- await generateBackup(this.hass, {
- agent_ids: ["backup.local"],
- });
- } catch (err) {
- showAlertDialog(this, { text: (err as Error).message });
+ if (!response) {
+ return;
}
await this._fetchBackupInfo();
diff --git a/src/panels/config/backup/ha-config-backup-locations.ts b/src/panels/config/backup/ha-config-backup-locations.ts
index 505f04b95b..a6623b1324 100644
--- a/src/panels/config/backup/ha-config-backup-locations.ts
+++ b/src/panels/config/backup/ha-config-backup-locations.ts
@@ -10,6 +10,7 @@ import { fetchBackupAgentsInfo } from "../../../data/backup";
import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
+import { domainToName } from "../../../data/integration";
@customElement("ha-config-backup-locations")
class HaConfigBackupLocations extends LitElement {
@@ -47,9 +48,10 @@ class HaConfigBackupLocations extends LitElement {
${this._agents.map((agent) => {
const [domain, name] = agent.agent_id.split(".");
- const domainName =
- this.hass.localize(`component.${domain}.title`) ||
- domain;
+ const domainName = domainToName(
+ this.hass.localize,
+ domain
+ );
return html`