diff --git a/src/panels/config/backup/components/ha-backup-agents-picker.ts b/src/panels/config/backup/components/ha-backup-agents-picker.ts
index 6a5fb7ca63f4..acf486dd6da5 100644
--- a/src/panels/config/backup/components/ha-backup-agents-picker.ts
+++ b/src/panels/config/backup/components/ha-backup-agents-picker.ts
@@ -1,18 +1,13 @@
import { mdiHarddisk } from "@mdi/js";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
-import memoizeOne from "memoize-one";
+import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import "../../../../components/ha-checkbox";
import "../../../../components/ha-formfield";
import "../../../../components/ha-svg-icon";
-import {
- compareAgents,
- computeBackupAgentName,
- isLocalAgent,
- type BackupAgent,
-} from "../../../../data/backup";
+import { computeBackupAgentName, isLocalAgent } from "../../../../data/backup";
import type { HomeAssistant } from "../../../../types";
import { brandsUrl } from "../../../../util/brands-url";
@@ -25,22 +20,18 @@ class HaBackupAgentsPicker extends LitElement {
public disabled = false;
@property({ attribute: false })
- public agents!: BackupAgent[];
+ public agentIds!: string[];
@property({ attribute: false })
- public disabledAgents?: string[];
+ public disabledAgentIds?: string[];
@property({ attribute: false })
public value!: string[];
- private _agentIds = memoizeOne((agents: BackupAgent[]) =>
- agents.map((agent) => agent.agent_id).sort(compareAgents)
- );
-
render() {
return html`
- ${this._agentIds(this.agents).map((agent) => this._renderAgent(agent))}
+ ${this.agentIds.map((agent) => this._renderAgent(agent))}
`;
}
@@ -50,15 +41,15 @@ class HaBackupAgentsPicker extends LitElement {
const name = computeBackupAgentName(
this.hass.localize,
agentId,
- this._agentIds(this.agents)
+ this.agentIds
);
const disabled =
- this.disabled || this.disabledAgents?.includes(agentId) || false;
+ this.disabled || this.disabledAgentIds?.includes(agentId) || false;
return html`
-
+
${isLocalAgent(agentId)
? html`
@@ -127,6 +118,12 @@ class HaBackupAgentsPicker extends LitElement {
line-height: 24px;
letter-spacing: 0.5px;
}
+ span.disabled {
+ color: var(--disabled-text-color);
+ }
+ span.disabled ha-svg-icon {
+ color: var(--disabled-text-color);
+ }
`;
}
diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts
index 34b8d2b4d8a6..eb5d53407e73 100644
--- a/src/panels/config/backup/dialogs/dialog-generate-backup.ts
+++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts
@@ -1,9 +1,10 @@
import { mdiClose } from "@mdi/js";
-import type { CSSResultGroup } from "lit";
+import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../common/dom/fire_event";
+import "../../../../components/ha-alert";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-expansion-panel";
@@ -17,20 +18,21 @@ import "../../../../components/ha-md-select";
import "../../../../components/ha-md-select-option";
import "../../../../components/ha-textfield";
import type {
- BackupAgent,
BackupConfig,
GenerateBackupParams,
} from "../../../../data/backup";
import {
+ CLOUD_AGENT,
+ compareAgents,
fetchBackupAgentsInfo,
fetchBackupConfig,
} 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-picker";
import "../components/config/ha-backup-config-data";
import type { BackupConfigData } from "../components/config/ha-backup-config-data";
+import "../components/ha-backup-agents-picker";
import type { GenerateBackupDialogParams } from "./show-dialog-generate-backup";
type FormData = {
@@ -54,13 +56,15 @@ const INITIAL_DATA: FormData = {
const STEPS = ["data", "sync"] as const;
+const DISALLOWED_AGENTS_NO_HA = [CLOUD_AGENT];
+
@customElement("ha-dialog-generate-backup")
class DialogGenerateBackup extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _step?: "data" | "sync";
- @state() private _agents: BackupAgent[] = [];
+ @state() private _agentIds: string[] = [];
@state() private _backupConfig?: BackupConfig;
@@ -74,6 +78,7 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
this._step = STEPS[0];
this._formData = INITIAL_DATA;
this._params = _params;
+
this._fetchAgents();
this._fetchBackupConfig();
}
@@ -84,7 +89,7 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
}
this._step = undefined;
this._formData = undefined;
- this._agents = [];
+ this._agentIds = [];
this._backupConfig = undefined;
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -92,7 +97,10 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
private async _fetchAgents() {
const { agents } = await fetchBackupAgentsInfo(this.hass);
- this._agents = agents;
+ this._agentIds = agents
+ .map((agent) => agent.agent_id)
+ .filter((id) => id !== CLOUD_AGENT || this._params?.cloudStatus.logged_in)
+ .sort(compareAgents);
}
private async _fetchBackupConfig() {
@@ -120,6 +128,32 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
this._step = STEPS[index + 1];
}
+ protected willUpdate(changedProperties: PropertyValues): void {
+ super.willUpdate(changedProperties);
+
+ if (changedProperties.has("_step")) {
+ if (this._step === "sync" && this._formData) {
+ const disallowedAgents = this._disabledAgentIds();
+ if (disallowedAgents.length) {
+ // Remove disallowed agents from the list
+ const agentsIds =
+ this._formData.agents_mode === "all"
+ ? this._agentIds
+ : this._formData.agent_ids;
+
+ const filteredAgents = agentsIds.filter(
+ (agentId) => !disallowedAgents.includes(agentId)
+ );
+ this._formData = {
+ ...this._formData,
+ agents_mode: "custom",
+ agent_ids: filteredAgents,
+ };
+ }
+ }
+ }
+ }
+
protected render() {
if (!this._step || !this._formData) {
return nothing;
@@ -131,6 +165,8 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
const isFirstStep = this._step === STEPS[0];
const isLastStep = this._step === STEPS[STEPS.length - 1];
+ const selectedAgents = this._formData.agent_ids;
+
return html`
@@ -159,7 +195,14 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
? html`Cancel`
: nothing}
${isLastStep
- ? html`Create backup`
+ ? html`
+
+ Create backup
+
+ `
: html`Next`}
@@ -194,6 +237,8 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
return nothing;
}
+ const disabledAgentIds = this._disabledAgentIds();
+
return html`
-
- All (${this._agents.length})
+
+ All (${this._agentIds.length})
Custom
@@ -223,6 +271,17 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
+ ${disabledAgentIds.length
+ ? html`
+
+ Add Home Assistant settings data to synchronize this backup to
+ Home Assistant Cloud.
+
+ `
+ : nothing}
${this._formData.agents_mode === "custom"
? html`
@@ -230,7 +289,8 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
.hass=${this.hass}
.value=${this._formData.agent_ids}
@value-changed=${this._agentsChanged}
- .agents=${this._agents}
+ .agentIds=${this._agentIds}
+ .disabledAgentIds=${disabledAgentIds}
>
`
@@ -260,6 +320,16 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
};
}
+ private _disabledAgentIds() {
+ if (!this._formData) {
+ return [];
+ }
+ const allAgents = this._agentIds;
+ return !this._formData.data.include_homeassistant
+ ? DISALLOWED_AGENTS_NO_HA.filter((agentId) => allAgents.includes(agentId))
+ : [];
+ }
+
private async _submit() {
if (!this._formData) {
return;
@@ -269,12 +339,10 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
const password = this._backupConfig?.create_backup.password || undefined;
- const ALL_AGENT_IDS = this._agents.map((agent) => agent.agent_id);
-
const params: GenerateBackupParams = {
name,
password,
- agent_ids: agents_mode === "all" ? ALL_AGENT_IDS : agent_ids,
+ agent_ids: agents_mode === "all" ? this._agentIds : agent_ids,
// We always include homeassistant if we include database
include_homeassistant:
data.include_homeassistant || data.include_database,
@@ -287,6 +355,13 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
params.include_addons = data.include_addons;
}
+ // Ensure we don't upload to disallowed agents if we are not including homeassistant
+ if (!params.include_homeassistant) {
+ params.agent_ids = params.agent_ids.filter(
+ (agentId) => !DISALLOWED_AGENTS_NO_HA.includes(agentId)
+ );
+ }
+
this._params!.submit?.(params);
this.closeDialog();
}
@@ -333,6 +408,10 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
.content {
padding-top: 0;
}
+ ha-alert {
+ margin-bottom: 16px;
+ display: block;
+ }
`,
];
}
diff --git a/src/panels/config/backup/dialogs/dialog-upload-backup.ts b/src/panels/config/backup/dialogs/dialog-upload-backup.ts
index 53809752e5bd..a9b3e9262ce9 100644
--- a/src/panels/config/backup/dialogs/dialog-upload-backup.ts
+++ b/src/panels/config/backup/dialogs/dialog-upload-backup.ts
@@ -2,7 +2,7 @@ import { mdiClose, mdiFolderUpload } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
-import { keyed } from "lit/directives/keyed";
+import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-alert";
import "../../../../components/ha-dialog-header";
@@ -11,12 +11,11 @@ import "../../../../components/ha-file-upload";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
-import "../../../../components/ha-md-list";
-import "../../../../components/ha-md-list-item";
-import "../../../../components/ha-md-select";
-import "../../../../components/ha-md-select-option";
-import type { BackupAgent } from "../../../../data/backup";
-import { fetchBackupAgentsInfo, uploadBackup } from "../../../../data/backup";
+import {
+ CORE_LOCAL_AGENT,
+ HASSIO_LOCAL_AGENT,
+ uploadBackup,
+} from "../../../../data/backup";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
@@ -27,14 +26,10 @@ import type { UploadBackupDialogParams } from "./show-dialog-upload-backup";
const SUPPORTED_FORMAT = "application/x-tar";
type FormData = {
- agents_mode: "all" | "custom";
- agent_ids: string[];
file?: File;
};
const INITIAL_DATA: FormData = {
- agents_mode: "all",
- agent_ids: [],
file: undefined,
};
@@ -51,8 +46,6 @@ export class DialogUploadBackup
@state() private _error?: string;
- @state() private _agents: BackupAgent[] = [];
-
@state() private _formData?: FormData;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
@@ -60,7 +53,6 @@ export class DialogUploadBackup
public async showDialog(params: UploadBackupDialogParams): Promise {
this._params = params;
this._formData = INITIAL_DATA;
- this._fetchAgents();
}
private _dialogClosed() {
@@ -68,7 +60,6 @@ export class DialogUploadBackup
this._params!.cancel();
}
this._formData = undefined;
- this._agents = [];
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -77,17 +68,8 @@ export class DialogUploadBackup
this._dialog?.close();
}
- private async _fetchAgents() {
- const { agents } = await fetchBackupAgentsInfo(this.hass);
- this._agents = agents;
- }
-
private _formValid() {
- return (
- this._formData?.file !== undefined &&
- (this._formData.agents_mode === "all" ||
- this._formData.agent_ids.length > 0)
- );
+ return this._formData?.file !== undefined;
}
protected render() {
@@ -117,44 +99,6 @@ export class DialogUploadBackup
supports="Supports .tar files"
@file-picked=${this._filePicked}
>
-
-
- Locations
-
- What locations you want to upload this backup.
-
- ${keyed(
- this._agents.length,
- html`
-
-
- All (${this._agents.length})
-
-
- Custom
-
-
- `
- )}
-
-
- ${this._formData.agents_mode === "custom"
- ? html`
-
-
-
- `
- : nothing}
${this._error
? html`${this._error}`
: nothing}
@@ -169,21 +113,6 @@ export class DialogUploadBackup
`;
}
- private _selectChanged(ev) {
- const select = ev.currentTarget;
- this._formData = {
- ...this._formData!,
- [select.id]: select.value,
- };
- }
-
- private _agentsChanged(ev) {
- this._formData = {
- ...this._formData!,
- agent_ids: ev.detail.value,
- };
- }
-
private async _filePicked(ev: CustomEvent<{ files: File[] }>): Promise {
this._error = undefined;
const file = ev.detail.files[0];
@@ -195,7 +124,7 @@ export class DialogUploadBackup
}
private async _upload() {
- const { file, agent_ids, agents_mode } = this._formData!;
+ const { file } = this._formData!;
if (!file || file.type !== SUPPORTED_FORMAT) {
showAlertDialog(this, {
title: "Unsupported file format",
@@ -205,14 +134,13 @@ export class DialogUploadBackup
return;
}
- const agents =
- agents_mode === "all"
- ? this._agents.map((agent) => agent.agent_id)
- : agent_ids;
+ const agentIds = isComponentLoaded(this.hass!, "hassio")
+ ? [HASSIO_LOCAL_AGENT]
+ : [CORE_LOCAL_AGENT];
this._uploading = true;
try {
- await uploadBackup(this.hass!, file, agents);
+ await uploadBackup(this.hass!, file, agentIds);
this._params!.submit?.();
this.closeDialog();
} catch (err: any) {
@@ -233,20 +161,6 @@ export class DialogUploadBackup
max-width: 500px;
max-height: 100%;
}
- ha-md-list {
- background: none;
- --md-list-item-leading-space: 0;
- --md-list-item-trailing-space: 0;
- }
- ha-md-select {
- min-width: 210px;
- }
- @media all and (max-width: 450px) {
- ha-md-select {
- min-width: 160px;
- width: 160px;
- }
- }
`,
];
}
diff --git a/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts b/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts
index c764290fa434..ccd57deb44c6 100644
--- a/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts
+++ b/src/panels/config/backup/dialogs/show-dialog-generate-backup.ts
@@ -1,9 +1,11 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { GenerateBackupParams } from "../../../../data/backup";
+import type { CloudStatus } from "../../../../data/cloud";
export interface GenerateBackupDialogParams {
submit?: (response: GenerateBackupParams) => void;
cancel?: () => void;
+ cloudStatus: CloudStatus;
}
export const loadGenerateBackupDialog = () =>
diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts
index 1c7cdefcce7b..ed478a577e32 100644
--- a/src/panels/config/backup/ha-config-backup-backups.ts
+++ b/src/panels/config/backup/ha-config-backup-backups.ts
@@ -363,7 +363,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
}
if (type === "manual") {
- const params = await showGenerateBackupDialog(this, {});
+ const params = await showGenerateBackupDialog(this, {
+ cloudStatus: this.cloudStatus,
+ });
if (!params) {
return;
diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts
index 16247dbcac3a..45cdf544853f 100644
--- a/src/panels/config/backup/ha-config-backup-overview.ts
+++ b/src/panels/config/backup/ha-config-backup-overview.ts
@@ -110,7 +110,9 @@ class HaConfigBackupOverview extends LitElement {
}
if (type === "manual") {
- const params = await showGenerateBackupDialog(this, {});
+ const params = await showGenerateBackupDialog(this, {
+ cloudStatus: this.cloudStatus,
+ });
if (!params) {
return;