mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 10:16:46 +00:00
Improve backup onboarding (#23282)
* Sort agents, enable cloud and local by default * Enable cloud by default * Improve wording * Hide fab if onboarding is not complete * Add recommended settings * Use one step encryption key during onboarding * Add description for cloud * Update change encryption key dialog
This commit is contained in:
parent
875ab0cb97
commit
d31f4a5f1d
@ -212,8 +212,12 @@ export const getPreferredAgentForDownload = (agents: string[]) => {
|
|||||||
return localAgents[0] || agents[0];
|
return localAgents[0] || agents[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CORE_LOCAL_AGENT = "backup.local";
|
||||||
|
export const HASSIO_LOCAL_AGENT = "backup.hassio";
|
||||||
|
export const CLOUD_AGENT = "cloud.cloud";
|
||||||
|
|
||||||
export const isLocalAgent = (agentId: string) =>
|
export const isLocalAgent = (agentId: string) =>
|
||||||
["backup.local", "hassio.local"].includes(agentId);
|
[CORE_LOCAL_AGENT, HASSIO_LOCAL_AGENT].includes(agentId);
|
||||||
|
|
||||||
export const computeBackupAgentName = (
|
export const computeBackupAgentName = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
@ -234,6 +238,12 @@ export const computeBackupAgentName = (
|
|||||||
return showName ? `${domainName}: ${name}` : domainName;
|
return showName ? `${domainName}: ${name}` : domainName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const compareAgents = (a: string, b: string) => {
|
||||||
|
const isLocalA = isLocalAgent(a);
|
||||||
|
const isLocalB = isLocalAgent(b);
|
||||||
|
return isLocalA === isLocalB ? a.localeCompare(b) : isLocalA ? -1 : 1;
|
||||||
|
};
|
||||||
|
|
||||||
export const generateEncryptionKey = () => {
|
export const generateEncryptionKey = () => {
|
||||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
const pattern = "xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx";
|
const pattern = "xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx";
|
||||||
|
@ -8,6 +8,7 @@ import "../../../../components/ha-checkbox";
|
|||||||
import "../../../../components/ha-formfield";
|
import "../../../../components/ha-formfield";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
|
compareAgents,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
type BackupAgent,
|
type BackupAgent,
|
||||||
@ -33,7 +34,7 @@ class HaBackupAgentsPicker extends LitElement {
|
|||||||
public value!: string[];
|
public value!: string[];
|
||||||
|
|
||||||
private _agentIds = memoizeOne((agents: BackupAgent[]) =>
|
private _agentIds = memoizeOne((agents: BackupAgent[]) =>
|
||||||
agents.map((agent) => agent.agent_id)
|
agents.map((agent) => agent.agent_id).sort(compareAgents)
|
||||||
);
|
);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import { mdiDatabase } from "@mdi/js";
|
import { mdiDatabase } from "@mdi/js";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||||
import "../../../../components/ha-md-list";
|
import "../../../../components/ha-md-list";
|
||||||
import "../../../../components/ha-md-list-item";
|
import "../../../../components/ha-md-list-item";
|
||||||
import "../../../../components/ha-switch";
|
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import type { BackupAgent } from "../../../../data/backup";
|
import "../../../../components/ha-switch";
|
||||||
import {
|
import {
|
||||||
|
CLOUD_AGENT,
|
||||||
|
compareAgents,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
fetchBackupAgentsInfo,
|
fetchBackupAgentsInfo,
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
} from "../../../../data/backup";
|
} from "../../../../data/backup";
|
||||||
|
import type { CloudStatus } from "../../../../data/cloud";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { brandsUrl } from "../../../../util/brands-url";
|
import { brandsUrl } from "../../../../util/brands-url";
|
||||||
|
|
||||||
@ -23,7 +25,9 @@ const DEFAULT_AGENTS = [];
|
|||||||
class HaBackupConfigAgents extends LitElement {
|
class HaBackupConfigAgents extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _agents: BackupAgent[] = [];
|
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
||||||
|
|
||||||
|
@state() private _agentIds: string[] = [];
|
||||||
|
|
||||||
@state() private value?: string[];
|
@state() private value?: string[];
|
||||||
|
|
||||||
@ -34,7 +38,10 @@ class HaBackupConfigAgents extends LitElement {
|
|||||||
|
|
||||||
private async _fetchAgents() {
|
private async _fetchAgents() {
|
||||||
const { agents } = await fetchBackupAgentsInfo(this.hass);
|
const { agents } = await fetchBackupAgentsInfo(this.hass);
|
||||||
this._agents = agents;
|
this._agentIds = agents
|
||||||
|
.map((agent) => agent.agent_id)
|
||||||
|
.filter((id) => id !== CLOUD_AGENT || this.cloudStatus.logged_in)
|
||||||
|
.sort(compareAgents);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
@ -42,18 +49,16 @@ class HaBackupConfigAgents extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const agentIds = this._agents.map((agent) => agent.agent_id);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${agentIds.length > 0
|
${this._agentIds.length > 0
|
||||||
? html`
|
? html`
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
${agentIds.map((agentId) => {
|
${this._agentIds.map((agentId) => {
|
||||||
const domain = computeDomain(agentId);
|
const domain = computeDomain(agentId);
|
||||||
const name = computeBackupAgentName(
|
const name = computeBackupAgentName(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
agentId,
|
agentId,
|
||||||
agentIds
|
this._agentIds
|
||||||
);
|
);
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
@ -77,6 +82,14 @@ class HaBackupConfigAgents extends LitElement {
|
|||||||
/>
|
/>
|
||||||
`}
|
`}
|
||||||
<div slot="headline">${name}</div>
|
<div slot="headline">${name}</div>
|
||||||
|
${agentId === CLOUD_AGENT
|
||||||
|
? html`
|
||||||
|
<div slot="supporting-text">
|
||||||
|
It stores one backup. The oldest backups are
|
||||||
|
deleted.
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
<ha-switch
|
<ha-switch
|
||||||
slot="end"
|
slot="end"
|
||||||
id=${agentId}
|
id=${agentId}
|
||||||
@ -104,9 +117,9 @@ class HaBackupConfigAgents extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure agents exist in the list
|
// Ensure agents exist in the list
|
||||||
this.value = this.value.filter((agent) =>
|
this.value = this.value
|
||||||
this._agents.some((a) => a.agent_id === agent)
|
.filter((agent) => this._agentIds.some((id) => id === agent))
|
||||||
);
|
.filter((id) => id !== CLOUD_AGENT || this.cloudStatus);
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
fireEvent(this, "value-changed", { value: this.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { mdiClose, mdiDownload, mdiKey } from "@mdi/js";
|
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-dialog-header";
|
import "../../../../components/ha-dialog-header";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import "../../../../components/ha-icon-button-prev";
|
import "../../../../components/ha-icon-button-prev";
|
||||||
|
import "../../../../components/ha-icon-next";
|
||||||
import "../../../../components/ha-md-dialog";
|
import "../../../../components/ha-md-dialog";
|
||||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||||
import "../../../../components/ha-md-list";
|
import "../../../../components/ha-md-list";
|
||||||
@ -20,7 +22,10 @@ import type {
|
|||||||
} from "../../../../data/backup";
|
} from "../../../../data/backup";
|
||||||
import {
|
import {
|
||||||
BackupScheduleState,
|
BackupScheduleState,
|
||||||
|
CLOUD_AGENT,
|
||||||
|
CORE_LOCAL_AGENT,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
|
HASSIO_LOCAL_AGENT,
|
||||||
updateBackupConfig,
|
updateBackupConfig,
|
||||||
} from "../../../../data/backup";
|
} from "../../../../data/backup";
|
||||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
@ -33,12 +38,12 @@ import "../components/ha-backup-config-data";
|
|||||||
import type { BackupConfigData } from "../components/ha-backup-config-data";
|
import type { BackupConfigData } from "../components/ha-backup-config-data";
|
||||||
import "../components/ha-backup-config-schedule";
|
import "../components/ha-backup-config-schedule";
|
||||||
import type { BackupConfigSchedule } from "../components/ha-backup-config-schedule";
|
import type { BackupConfigSchedule } from "../components/ha-backup-config-schedule";
|
||||||
import type { SetBackupEncryptionKeyDialogParams } from "./show-dialog-set-backup-encryption-key";
|
import type { BackupOnboardingDialogParams } from "./show-dialog-backup_onboarding";
|
||||||
|
|
||||||
const STEPS = [
|
const STEPS = [
|
||||||
"welcome",
|
"welcome",
|
||||||
"new_key",
|
"key",
|
||||||
"save_key",
|
"setup",
|
||||||
"schedule",
|
"schedule",
|
||||||
"data",
|
"data",
|
||||||
"locations",
|
"locations",
|
||||||
@ -46,7 +51,9 @@ const STEPS = [
|
|||||||
|
|
||||||
type Step = (typeof STEPS)[number];
|
type Step = (typeof STEPS)[number];
|
||||||
|
|
||||||
const INITIAL_CONFIG: BackupConfig = {
|
const FULL_DIALOG_STEPS = new Set<Step>(["setup"]);
|
||||||
|
|
||||||
|
const RECOMMENDED_CONFIG: BackupConfig = {
|
||||||
create_backup: {
|
create_backup: {
|
||||||
agent_ids: [],
|
agent_ids: [],
|
||||||
include_folders: [],
|
include_folders: [],
|
||||||
@ -68,27 +75,37 @@ const INITIAL_CONFIG: BackupConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-dialog-backup-onboarding")
|
@customElement("ha-dialog-backup-onboarding")
|
||||||
class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@state() private _step?: Step;
|
@state() private _step?: Step;
|
||||||
|
|
||||||
@state() private _params?: SetBackupEncryptionKeyDialogParams;
|
@state() private _params?: BackupOnboardingDialogParams;
|
||||||
|
|
||||||
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
||||||
|
|
||||||
@state() private _config?: BackupConfig;
|
@state() private _config?: BackupConfig;
|
||||||
|
|
||||||
private _suggestedEncryptionKey?: string;
|
public showDialog(params: BackupOnboardingDialogParams): void {
|
||||||
|
|
||||||
public showDialog(params: SetBackupEncryptionKeyDialogParams): void {
|
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._step = STEPS[0];
|
this._step = STEPS[0];
|
||||||
this._config = INITIAL_CONFIG;
|
this._config = RECOMMENDED_CONFIG;
|
||||||
|
|
||||||
|
// Enable local location by default
|
||||||
|
if (isComponentLoaded(this.hass, "hassio")) {
|
||||||
|
this._config.create_backup.agent_ids.push(HASSIO_LOCAL_AGENT);
|
||||||
|
} else {
|
||||||
|
this._config.create_backup.agent_ids.push(CORE_LOCAL_AGENT);
|
||||||
|
}
|
||||||
|
// Enable cloud location if logged in
|
||||||
|
if (this._params.cloudStatus.logged_in) {
|
||||||
|
this._config.create_backup.agent_ids.push(CLOUD_AGENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config.create_backup.password = generateEncryptionKey();
|
||||||
this._opened = true;
|
this._opened = true;
|
||||||
this._suggestedEncryptionKey = generateEncryptionKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
@ -102,7 +119,6 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
this._step = undefined;
|
this._step = undefined;
|
||||||
this._config = undefined;
|
this._config = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._suggestedEncryptionKey = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _done() {
|
private async _done() {
|
||||||
@ -158,7 +174,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._opened || !this._params) {
|
if (!this._opened || !this._params || !this._step) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,25 +203,29 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
<span slot="title">${this._stepTitle}</span>
|
<span slot="title">${this._stepTitle}</span>
|
||||||
</ha-dialog-header>
|
</ha-dialog-header>
|
||||||
<div slot="content">${this._renderStepContent()}</div>
|
<div slot="content">${this._renderStepContent()}</div>
|
||||||
<div slot="actions">
|
${!FULL_DIALOG_STEPS.has(this._step)
|
||||||
${isLastStep
|
? html`
|
||||||
? html`
|
<div slot="actions">
|
||||||
<ha-button
|
${isLastStep
|
||||||
@click=${this._done}
|
? html`
|
||||||
.disabled=${!this._isStepValid()}
|
<ha-button
|
||||||
>
|
@click=${this._done}
|
||||||
Save
|
.disabled=${!this._isStepValid()}
|
||||||
</ha-button>
|
>
|
||||||
`
|
Save
|
||||||
: html`
|
</ha-button>
|
||||||
<ha-button
|
`
|
||||||
@click=${this._nextStep}
|
: html`
|
||||||
.disabled=${!this._isStepValid()}
|
<ha-button
|
||||||
>
|
@click=${this._nextStep}
|
||||||
Next
|
.disabled=${!this._isStepValid()}
|
||||||
</ha-button>
|
>
|
||||||
`}
|
Next
|
||||||
</div>
|
</ha-button>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
</ha-md-dialog>
|
</ha-md-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -214,10 +234,10 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
switch (this._step) {
|
switch (this._step) {
|
||||||
case "welcome":
|
case "welcome":
|
||||||
return "";
|
return "";
|
||||||
case "new_key":
|
case "key":
|
||||||
return "Encryption key";
|
return "Encryption key";
|
||||||
case "save_key":
|
case "setup":
|
||||||
return "Save encryption key";
|
return "Set up your backup strategy";
|
||||||
case "schedule":
|
case "schedule":
|
||||||
return "Automatic backups";
|
return "Automatic backups";
|
||||||
case "data":
|
case "data":
|
||||||
@ -231,9 +251,9 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
|
|
||||||
private _isStepValid(): boolean {
|
private _isStepValid(): boolean {
|
||||||
switch (this._step) {
|
switch (this._step) {
|
||||||
case "new_key":
|
case "key":
|
||||||
return !!this._config?.create_backup.password;
|
return true;
|
||||||
case "save_key":
|
case "setup":
|
||||||
return true;
|
return true;
|
||||||
case "schedule":
|
case "schedule":
|
||||||
return !!this._config?.schedule;
|
return !!this._config?.schedule;
|
||||||
@ -269,37 +289,20 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
case "new_key":
|
case "key":
|
||||||
return html`
|
return html`
|
||||||
<p>
|
<p>
|
||||||
All your backups are encrypted to keep your data private and secure.
|
All your backups are encrypted to keep your data private and secure.
|
||||||
You need this encryption key to restore any backup.
|
We recommend to save this key somewhere secure. As you can only
|
||||||
</p>
|
restore your data with the backup encryption key.
|
||||||
<ha-password-field
|
|
||||||
placeholder="New encryption key"
|
|
||||||
@input=${this._encryptionKeyChanged}
|
|
||||||
.value=${this._config.create_backup.password ?? ""}
|
|
||||||
></ha-password-field>
|
|
||||||
<ha-md-list>
|
|
||||||
<ha-md-list-item>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiKey}></ha-svg-icon>
|
|
||||||
<span slot="headline">Use suggested encryption key</span>
|
|
||||||
<span slot="supporting-text">
|
|
||||||
${this._suggestedEncryptionKey}
|
|
||||||
</span>
|
|
||||||
<ha-button slot="end" @click=${this._useSuggestedEncryptionKey}>
|
|
||||||
Enter
|
|
||||||
</ha-button>
|
|
||||||
</ha-md-list-item>
|
|
||||||
</ha-md-list>
|
|
||||||
`;
|
|
||||||
case "save_key":
|
|
||||||
return html`
|
|
||||||
<p>
|
|
||||||
It’s important that you don’t lose this encryption key. We recommend
|
|
||||||
to save this key somewhere secure. As you can only restore your data
|
|
||||||
with the backup encryption key.
|
|
||||||
</p>
|
</p>
|
||||||
|
<div class="encryption-key">
|
||||||
|
<p>${this._config.create_backup.password}</p>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiContentCopy}
|
||||||
|
@click=${this._copyKeyToClipboard}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Download emergency kit</span>
|
<span slot="headline">Download emergency kit</span>
|
||||||
@ -313,6 +316,29 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
`;
|
`;
|
||||||
|
case "setup":
|
||||||
|
return html`
|
||||||
|
<p>
|
||||||
|
It is recommended to create a daily backup and keep copies of the
|
||||||
|
last 3 days on two different locations. And one of them is off-site.
|
||||||
|
</p>
|
||||||
|
<ha-md-list class="full">
|
||||||
|
<ha-md-list-item type="button" @click=${this._done}>
|
||||||
|
<span slot="headline">Recommended settings</span>
|
||||||
|
<span slot="supporting-text">
|
||||||
|
Set the proven backup strategy of daily backup.
|
||||||
|
</span>
|
||||||
|
<ha-icon-next slot="end"> </ha-icon-next>
|
||||||
|
</ha-md-list-item>
|
||||||
|
<ha-md-list-item type="button" @click=${this._nextStep}>
|
||||||
|
<span slot="headline">Custom settings</span>
|
||||||
|
<span slot="supporting-text">
|
||||||
|
Select your own automation, data and locations
|
||||||
|
</span>
|
||||||
|
<ha-icon-next slot="end"> </ha-icon-next>
|
||||||
|
</ha-md-list-item>
|
||||||
|
</ha-md-list>
|
||||||
|
`;
|
||||||
case "schedule":
|
case "schedule":
|
||||||
return html`
|
return html`
|
||||||
<p>
|
<p>
|
||||||
@ -347,6 +373,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
<ha-backup-config-agents
|
<ha-backup-config-agents
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._config.create_backup.agent_ids}
|
.value=${this._config.create_backup.agent_ids}
|
||||||
|
.cloudStatus=${this._params!.cloudStatus}
|
||||||
@value-changed=${this._agentsConfigChanged}
|
@value-changed=${this._agentsConfigChanged}
|
||||||
></ha-backup-config-agents>
|
></ha-backup-config-agents>
|
||||||
`;
|
`;
|
||||||
@ -365,23 +392,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _encryptionKeyChanged(ev) {
|
private _copyKeyToClipboard() {
|
||||||
const value = ev.target.value;
|
copyToClipboard(this._config!.create_backup.password!);
|
||||||
this._setEncryptionKey(value);
|
showToast(this, {
|
||||||
}
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
|
});
|
||||||
private _useSuggestedEncryptionKey() {
|
|
||||||
this._setEncryptionKey(this._suggestedEncryptionKey!);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setEncryptionKey(value: string) {
|
|
||||||
this._config = {
|
|
||||||
...this._config!,
|
|
||||||
create_backup: {
|
|
||||||
...this._config!.create_backup,
|
|
||||||
password: value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dataConfig(config: BackupConfig): BackupConfigData {
|
private _dataConfig(config: BackupConfig): BackupConfigData {
|
||||||
@ -442,7 +457,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
css`
|
css`
|
||||||
ha-md-dialog {
|
ha-md-dialog {
|
||||||
width: 90vw;
|
width: 90vw;
|
||||||
max-width: 500px;
|
max-width: 560px;
|
||||||
}
|
}
|
||||||
div[slot="content"] {
|
div[slot="content"] {
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
@ -452,6 +467,12 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
--md-list-item-leading-space: 0;
|
--md-list-item-leading-space: 0;
|
||||||
--md-list-item-trailing-space: 0;
|
--md-list-item-trailing-space: 0;
|
||||||
}
|
}
|
||||||
|
ha-md-list.full {
|
||||||
|
--md-list-item-leading-space: 24px;
|
||||||
|
--md-list-item-trailing-space: 24px;
|
||||||
|
margin-left: -24px;
|
||||||
|
margin-right: -24px;
|
||||||
|
}
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
ha-md-dialog {
|
ha-md-dialog {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
@ -466,6 +487,30 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
.welcome {
|
.welcome {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.encryption-key {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.encryption-key p {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
font-family: "Roboto Mono", "Consolas", "Menlo", monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.encryption-key ha-icon-button {
|
||||||
|
flex: none;
|
||||||
|
margin: -16px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -473,6 +518,6 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-dialog-backup-onboarding": DialogSetBackupEncryptionKey;
|
"ha-dialog-backup-onboarding": DialogBackupOnboarding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { mdiClose, mdiDownload, mdiKey } from "@mdi/js";
|
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-dialog-header";
|
import "../../../../components/ha-dialog-header";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
@ -17,9 +18,12 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
|||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { fileDownload } from "../../../../util/file_download";
|
import { fileDownload } from "../../../../util/file_download";
|
||||||
|
import { showToast } from "../../../../util/toast";
|
||||||
import type { ChangeBackupEncryptionKeyDialogParams } from "./show-dialog-change-backup-encryption-key";
|
import type { ChangeBackupEncryptionKeyDialogParams } from "./show-dialog-change-backup-encryption-key";
|
||||||
|
|
||||||
const STEPS = ["current", "new", "save"] as const;
|
const STEPS = ["current", "new", "done"] as const;
|
||||||
|
|
||||||
|
type Step = (typeof STEPS)[number];
|
||||||
|
|
||||||
@customElement("ha-dialog-change-backup-encryption-key")
|
@customElement("ha-dialog-change-backup-encryption-key")
|
||||||
class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||||
@ -27,7 +31,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
|
|
||||||
@state() private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@state() private _step?: "current" | "new" | "save";
|
@state() private _step?: Step;
|
||||||
|
|
||||||
@state() private _params?: ChangeBackupEncryptionKeyDialogParams;
|
@state() private _params?: ChangeBackupEncryptionKeyDialogParams;
|
||||||
|
|
||||||
@ -35,13 +39,11 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
|
|
||||||
@state() private _newEncryptionKey?: string;
|
@state() private _newEncryptionKey?: string;
|
||||||
|
|
||||||
private _suggestedEncryptionKey?: string;
|
|
||||||
|
|
||||||
public showDialog(params: ChangeBackupEncryptionKeyDialogParams): void {
|
public showDialog(params: ChangeBackupEncryptionKeyDialogParams): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._step = STEPS[0];
|
this._step = STEPS[0];
|
||||||
this._opened = true;
|
this._opened = true;
|
||||||
this._suggestedEncryptionKey = generateEncryptionKey();
|
this._newEncryptionKey = generateEncryptionKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
@ -55,7 +57,6 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
this._step = undefined;
|
this._step = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._newEncryptionKey = undefined;
|
this._newEncryptionKey = undefined;
|
||||||
this._suggestedEncryptionKey = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _done() {
|
private _done() {
|
||||||
@ -89,7 +90,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
? "Save current encryption key"
|
? "Save current encryption key"
|
||||||
: this._step === "new"
|
: this._step === "new"
|
||||||
? "New encryption key"
|
? "New encryption key"
|
||||||
: "Save new encryption key";
|
: "";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||||
@ -120,13 +121,12 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
<ha-button
|
<ha-button
|
||||||
@click=${this._submit}
|
@click=${this._submit}
|
||||||
.disabled=${!this._newEncryptionKey}
|
.disabled=${!this._newEncryptionKey}
|
||||||
|
class="danger"
|
||||||
>
|
>
|
||||||
Change encryption key
|
Change encryption key
|
||||||
</ha-button>
|
</ha-button>
|
||||||
`
|
`
|
||||||
: this._step === "save"
|
: html`<ha-button @click=${this._done}>Done</ha-button>`}
|
||||||
? html`<ha-button @click=${this._done}>Done</ha-button>`
|
|
||||||
: nothing}
|
|
||||||
</div>
|
</div>
|
||||||
</ha-md-dialog>
|
</ha-md-dialog>
|
||||||
`;
|
`;
|
||||||
@ -143,7 +143,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
</p>
|
</p>
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Download emergency kit</span>
|
<span slot="headline">Download old emergency kit</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
We recommend to save this encryption key somewhere secure.
|
We recommend to save this encryption key somewhere secure.
|
||||||
</span>
|
</span>
|
||||||
@ -155,36 +155,22 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
`;
|
`;
|
||||||
case "new":
|
case "new":
|
||||||
return html`
|
|
||||||
<p>All next backups will use the new encryption key.</p>
|
|
||||||
<ha-password-field
|
|
||||||
placeholder="New encryption key"
|
|
||||||
@input=${this._encryptionKeyChanged}
|
|
||||||
.value=${this._newEncryptionKey || ""}
|
|
||||||
></ha-password-field>
|
|
||||||
<ha-md-list>
|
|
||||||
<ha-md-list-item>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiKey}></ha-svg-icon>
|
|
||||||
<span slot="headline">Use suggested encryption key</span>
|
|
||||||
<span slot="supporting-text">
|
|
||||||
${this._suggestedEncryptionKey}
|
|
||||||
</span>
|
|
||||||
<ha-button slot="end" @click=${this._useSuggestedEncryptionKey}>
|
|
||||||
Enter
|
|
||||||
</ha-button>
|
|
||||||
</ha-md-list-item>
|
|
||||||
</ha-md-list>
|
|
||||||
`;
|
|
||||||
case "save":
|
|
||||||
return html`
|
return html`
|
||||||
<p>
|
<p>
|
||||||
It’s important that you don’t lose this encryption key. We recommend
|
All next backups will use the new encryption key. We recommend to
|
||||||
to save this key somewhere secure. As you can only restore your data
|
save this key somewhere secure. As you can only restore your data
|
||||||
with the backup encryption key.
|
with the backup encryption key.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="encryption-key">
|
||||||
|
<p>${this._newEncryptionKey}</p>
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiContentCopy}
|
||||||
|
@click=${this._copyKeyToClipboard}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Download emergency kit</span>
|
<span slot="headline">Download new emergency kit</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
We recommend to save this encryption key somewhere secure.
|
We recommend to save this encryption key somewhere secure.
|
||||||
</span>
|
</span>
|
||||||
@ -195,10 +181,27 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
`;
|
`;
|
||||||
|
case "done":
|
||||||
|
return html`
|
||||||
|
<div class="done">
|
||||||
|
<img
|
||||||
|
src="/static/images/voice-assistant/hi.png"
|
||||||
|
alt="Casita Home Assistant logo"
|
||||||
|
/>
|
||||||
|
<p>Encryption key changed</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _copyKeyToClipboard() {
|
||||||
|
copyToClipboard(this._newEncryptionKey);
|
||||||
|
showToast(this, {
|
||||||
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _downloadOld() {
|
private _downloadOld() {
|
||||||
if (!this._params?.currentKey) {
|
if (!this._params?.currentKey) {
|
||||||
return;
|
return;
|
||||||
@ -221,14 +224,6 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _encryptionKeyChanged(ev) {
|
|
||||||
this._newEncryptionKey = ev.target.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _useSuggestedEncryptionKey() {
|
|
||||||
this._newEncryptionKey = this._suggestedEncryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _submit() {
|
private async _submit() {
|
||||||
if (!this._newEncryptionKey) {
|
if (!this._newEncryptionKey) {
|
||||||
return;
|
return;
|
||||||
@ -244,7 +239,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
css`
|
css`
|
||||||
ha-md-dialog {
|
ha-md-dialog {
|
||||||
width: 90vw;
|
width: 90vw;
|
||||||
max-width: 500px;
|
max-width: 560px;
|
||||||
}
|
}
|
||||||
div[slot="content"] {
|
div[slot="content"] {
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
@ -254,6 +249,33 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
--md-list-item-leading-space: 0;
|
--md-list-item-leading-space: 0;
|
||||||
--md-list-item-trailing-space: 0;
|
--md-list-item-trailing-space: 0;
|
||||||
}
|
}
|
||||||
|
ha-button.danger {
|
||||||
|
--mdc-theme-primary: var(--error-color);
|
||||||
|
}
|
||||||
|
.encryption-key {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
.encryption-key p {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
font-family: "Roboto Mono", "Consolas", "Menlo", monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.encryption-key ha-icon-button {
|
||||||
|
flex: none;
|
||||||
|
margin: -16px;
|
||||||
|
}
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
ha-md-dialog {
|
ha-md-dialog {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
@ -265,6 +287,13 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
p {
|
p {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
.done {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 22px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import type { CloudStatus } from "../../../../data/cloud";
|
||||||
|
|
||||||
export interface BackupOnboardingDialogParams {
|
export interface BackupOnboardingDialogParams {
|
||||||
submit?: (value: boolean) => void;
|
submit?: (value: boolean) => void;
|
||||||
cancel?: () => void;
|
cancel?: () => void;
|
||||||
|
cloudStatus: CloudStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadDialog = () => import("./dialog-backup-onboarding");
|
const loadDialog = () => import("./dialog-backup-onboarding");
|
||||||
|
@ -18,6 +18,7 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
|||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
|
import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter";
|
||||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import type {
|
import type {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
@ -36,6 +37,7 @@ import "../../../components/ha-svg-icon";
|
|||||||
import { getSignedPath } from "../../../data/auth";
|
import { getSignedPath } from "../../../data/auth";
|
||||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||||
import {
|
import {
|
||||||
|
compareAgents,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
deleteBackup,
|
deleteBackup,
|
||||||
fetchBackupConfig,
|
fetchBackupConfig,
|
||||||
@ -51,6 +53,7 @@ import {
|
|||||||
DEFAULT_MANAGER_STATE,
|
DEFAULT_MANAGER_STATE,
|
||||||
subscribeBackupEvents,
|
subscribeBackupEvents,
|
||||||
} from "../../../data/backup_manager";
|
} from "../../../data/backup_manager";
|
||||||
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
@ -72,7 +75,6 @@ import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboard
|
|||||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||||
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
||||||
import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter";
|
|
||||||
|
|
||||||
interface BackupRow extends BackupContent {
|
interface BackupRow extends BackupContent {
|
||||||
formatted_type: string;
|
formatted_type: string;
|
||||||
@ -86,6 +88,8 @@ const TYPE_ORDER: Array<BackupType> = ["strategy", "custom"];
|
|||||||
class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
@ -331,7 +335,7 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
|||||||
slot="action"
|
slot="action"
|
||||||
@click=${this._setupBackupStrategy}
|
@click=${this._setupBackupStrategy}
|
||||||
>
|
>
|
||||||
Setup backup strategy
|
Set up backup strategy
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`
|
`
|
||||||
@ -401,16 +405,21 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
|||||||
</div>
|
</div>
|
||||||
</div> `
|
</div> `
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${!this._needsOnboarding
|
||||||
<ha-fab
|
? html`
|
||||||
slot="fab"
|
<ha-fab
|
||||||
?disabled=${backupInProgress}
|
slot="fab"
|
||||||
.label=${this.hass.localize("ui.panel.config.backup.create_backup")}
|
?disabled=${backupInProgress}
|
||||||
extended
|
.label=${this.hass.localize(
|
||||||
@click=${this._newBackup}
|
"ui.panel.config.backup.create_backup"
|
||||||
>
|
)}
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
extended
|
||||||
</ha-fab>
|
@click=${this._newBackup}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</ha-fab>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
</hass-tabs-subpage-data-table>
|
</hass-tabs-subpage-data-table>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -480,7 +489,10 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private async _fetchBackupInfo() {
|
private async _fetchBackupInfo() {
|
||||||
const info = await fetchBackupInfo(this.hass);
|
const info = await fetchBackupInfo(this.hass);
|
||||||
this._backups = info.backups;
|
this._backups = info.backups.map((backup) => ({
|
||||||
|
...backup,
|
||||||
|
agent_ids: backup.agent_ids?.sort(compareAgents),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchBackupConfig() {
|
private async _fetchBackupConfig() {
|
||||||
@ -489,7 +501,7 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get _needsOnboarding() {
|
private get _needsOnboarding() {
|
||||||
return this._config && !this._config.create_backup.password;
|
return !this._config?.create_backup.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _uploadBackup(ev) {
|
private async _uploadBackup(ev) {
|
||||||
@ -501,15 +513,6 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _newBackup(): Promise<void> {
|
private async _newBackup(): Promise<void> {
|
||||||
if (this._needsOnboarding) {
|
|
||||||
const success = await showBackupOnboardingDialog(this, {});
|
|
||||||
if (!success) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._fetchBackupConfig();
|
|
||||||
|
|
||||||
const config = this._config!;
|
const config = this._config!;
|
||||||
|
|
||||||
const type = await showNewBackupDialog(this, { config });
|
const type = await showNewBackupDialog(this, { config });
|
||||||
@ -603,12 +606,16 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _setupBackupStrategy() {
|
private async _setupBackupStrategy() {
|
||||||
const success = await showBackupOnboardingDialog(this, {});
|
const success = await showBackupOnboardingDialog(this, {
|
||||||
|
cloudStatus: this.cloudStatus,
|
||||||
|
});
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._fetchBackupConfig();
|
this._fetchBackupConfig();
|
||||||
|
await generateBackupWithStrategySettings(this.hass);
|
||||||
|
await this._fetchBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -17,6 +17,7 @@ import "../../../components/ha-md-list-item";
|
|||||||
import { getSignedPath } from "../../../data/auth";
|
import { getSignedPath } from "../../../data/auth";
|
||||||
import type { BackupContentExtended } from "../../../data/backup";
|
import type { BackupContentExtended } from "../../../data/backup";
|
||||||
import {
|
import {
|
||||||
|
compareAgents,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
deleteBackup,
|
deleteBackup,
|
||||||
fetchBackupDetails,
|
fetchBackupDetails,
|
||||||
@ -265,7 +266,10 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
private async _fetchBackup() {
|
private async _fetchBackup() {
|
||||||
try {
|
try {
|
||||||
const response = await fetchBackupDetails(this.hass, this.backupId);
|
const response = await fetchBackupDetails(this.hass, this.backupId);
|
||||||
this._backup = response.backup;
|
this._backup = {
|
||||||
|
...response.backup,
|
||||||
|
agent_ids: response.backup.agent_ids?.sort(compareAgents),
|
||||||
|
};
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = err?.message || "Could not fetch backup details";
|
this._error = err?.message || "Could not fetch backup details";
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
fetchBackupConfig,
|
fetchBackupConfig,
|
||||||
updateBackupConfig,
|
updateBackupConfig,
|
||||||
} from "../../../data/backup";
|
} from "../../../data/backup";
|
||||||
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import "./components/ha-backup-config-agents";
|
import "./components/ha-backup-config-agents";
|
||||||
@ -46,6 +47,8 @@ const INITIAL_BACKUP_CONFIG: BackupConfig = {
|
|||||||
class HaConfigBackupStrategy extends LitElement {
|
class HaConfigBackupStrategy extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@state() private _backupConfig: BackupConfig = INITIAL_BACKUP_CONFIG;
|
@state() private _backupConfig: BackupConfig = INITIAL_BACKUP_CONFIG;
|
||||||
@ -111,6 +114,7 @@ class HaConfigBackupStrategy extends LitElement {
|
|||||||
<ha-backup-config-agents
|
<ha-backup-config-agents
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._backupConfig.create_backup.agent_ids}
|
.value=${this._backupConfig.create_backup.agent_ids}
|
||||||
|
.cloudStatus=${this.cloudStatus}
|
||||||
@value-changed=${this._agentsConfigChanged}
|
@value-changed=${this._agentsConfigChanged}
|
||||||
></ha-backup-config-agents>
|
></ha-backup-config-agents>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import type { RouterOptions } from "../../../layouts/hass-router-page";
|
import type { RouterOptions } from "../../../layouts/hass-router-page";
|
||||||
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
@ -10,6 +11,8 @@ import "./ha-config-backup-dashboard";
|
|||||||
class HaConfigBackup extends HassRouterPage {
|
class HaConfigBackup extends HassRouterPage {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
protected routerOptions: RouterOptions = {
|
protected routerOptions: RouterOptions = {
|
||||||
@ -38,6 +41,7 @@ class HaConfigBackup extends HassRouterPage {
|
|||||||
pageEl.hass = this.hass;
|
pageEl.hass = this.hass;
|
||||||
pageEl.route = this.routeTail;
|
pageEl.route = this.routeTail;
|
||||||
pageEl.narrow = this.narrow;
|
pageEl.narrow = this.narrow;
|
||||||
|
pageEl.cloudStatus = this.cloudStatus;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!changedProps || changedProps.has("route")) &&
|
(!changedProps || changedProps.has("route")) &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user