mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 21:06:34 +00:00
Add overview summary for backups (#23343)
This commit is contained in:
parent
b693fd1edc
commit
92b02e39c9
@ -1,5 +1,5 @@
|
||||
import {
|
||||
mdiAlertCircleCheckOutline,
|
||||
mdiAlertCircleOutline,
|
||||
mdiAlertOutline,
|
||||
mdiCheck,
|
||||
mdiInformationOutline,
|
||||
@ -16,7 +16,7 @@ type SummaryStatus = "success" | "error" | "info" | "warning" | "loading";
|
||||
|
||||
const ICONS: Record<SummaryStatus, string> = {
|
||||
success: mdiCheck,
|
||||
error: mdiAlertCircleCheckOutline,
|
||||
error: mdiAlertCircleOutline,
|
||||
warning: mdiAlertOutline,
|
||||
info: mdiInformationOutline,
|
||||
loading: mdiSync,
|
||||
@ -60,6 +60,9 @@ class HaBackupSummaryCard extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@ -71,7 +74,7 @@ class HaBackupSummaryCard extends LitElement {
|
||||
column-gap: 16px;
|
||||
row-gap: 8px;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { mdiCalendarSync, mdiGestureTap } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@ -62,6 +63,7 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="link" href="/config/backup/backups">
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${automaticStats.count} automatic backups
|
||||
</div>
|
||||
@ -71,6 +73,7 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="link" href="/config/backup/backups">
|
||||
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||
<div slot="headline">${manualStats.count} manual backups</div>
|
||||
<div slot="supporting-text">
|
||||
${bytesToString(manualStats.size, 1)} in total
|
||||
|
@ -0,0 +1,104 @@
|
||||
import { mdiInformationOutline } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"button-click": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-backup-overview-onboarding")
|
||||
class HaBackupOverviewBackups extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
private async _setup() {
|
||||
fireEvent(this, "button-click");
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
<div class="icon">
|
||||
<ha-svg-icon .path=${mdiInformationOutline}></ha-svg-icon>
|
||||
</div>
|
||||
Set up automatic backups
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Backups are essential to a reliable smart home. They protect your
|
||||
setup against failures and allows you to quickly have a working
|
||||
system again. 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>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._setup}>
|
||||
Set up automatic backups
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.icon {
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.icon::before {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.2;
|
||||
}
|
||||
.icon ha-svg-icon {
|
||||
color: var(--primary-color);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border-top: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-backup-overview-onboarding": HaBackupOverviewBackups;
|
||||
}
|
||||
}
|
@ -1,18 +1,15 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { ManagerStateEvent } from "../../../../data/backup_manager";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-backup-summary-card";
|
||||
import type { ManagerStateEvent } from "../../../../../data/backup_manager";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-backup-summary-card";
|
||||
|
||||
@customElement("ha-backup-summary-progress")
|
||||
export class HaBackupSummaryProgress extends LitElement {
|
||||
@customElement("ha-backup-overview-progress")
|
||||
export class HaBackupOverviewProgress extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||
|
||||
@property({ type: Boolean, attribute: "has-action" })
|
||||
public hasAction = false;
|
||||
|
||||
private get _heading() {
|
||||
switch (this.manager.manager_state) {
|
||||
case "create_backup":
|
||||
@ -93,9 +90,7 @@ export class HaBackupSummaryProgress extends LitElement {
|
||||
.heading=${this._heading}
|
||||
.description=${this._description}
|
||||
status="loading"
|
||||
.hasAction=${this.hasAction}
|
||||
>
|
||||
<slot name="action" slot="action"></slot>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
@ -103,6 +98,6 @@ export class HaBackupSummaryProgress extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-backup-summary-progress": HaBackupSummaryProgress;
|
||||
"ha-backup-overview-progress": HaBackupOverviewProgress;
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
||||
import { differenceInDays, setHours, setMinutes } from "date-fns";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
||||
import { BackupScheduleState } from "../../../../../data/backup";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-backup-summary-card";
|
||||
|
||||
@customElement("ha-backup-overview-summary")
|
||||
class HaBackupOverviewBackups extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||
|
||||
@property({ attribute: false }) public config!: BackupConfig;
|
||||
|
||||
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
||||
const sortedBackups = backups
|
||||
.filter((backup) => backup.with_automatic_settings)
|
||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||
|
||||
return sortedBackups[0] as BackupContent | undefined;
|
||||
});
|
||||
|
||||
private _nextBackupDescription(schedule: BackupScheduleState) {
|
||||
const newDate = setMinutes(setHours(new Date(), 4), 45);
|
||||
const time = formatTime(newDate, this.hass.locale, this.hass.config);
|
||||
|
||||
switch (schedule) {
|
||||
case BackupScheduleState.DAILY:
|
||||
return `Next automatic backup tomorrow at ${time}`;
|
||||
case BackupScheduleState.MONDAY:
|
||||
return `Next automatic backup next Monday at ${time}`;
|
||||
case BackupScheduleState.TUESDAY:
|
||||
return `Next automatic backup next Thuesday at ${time}`;
|
||||
case BackupScheduleState.WEDNESDAY:
|
||||
return `Next automatic backup next Wednesday at ${time}`;
|
||||
case BackupScheduleState.THURSDAY:
|
||||
return `Next automatic backup next Thursday at ${time}`;
|
||||
case BackupScheduleState.FRIDAY:
|
||||
return `Next automatic backup next Friday at ${time}`;
|
||||
case BackupScheduleState.SATURDAY:
|
||||
return `Next automatic backup next Saturday at ${time}`;
|
||||
case BackupScheduleState.SUNDAY:
|
||||
return `Next automatic backup next Sunday at ${time}`;
|
||||
default:
|
||||
return "No automatic backup scheduled";
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const lastBackup = this._lastBackup(this.backups);
|
||||
|
||||
if (!lastBackup) {
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading="No automatic backup available"
|
||||
description="You have no automatic backups yet."
|
||||
status="warning"
|
||||
>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
|
||||
const lastBackupDate = new Date(lastBackup.date);
|
||||
|
||||
const numberOfDays = differenceInDays(new Date(), lastBackupDate);
|
||||
const now = new Date();
|
||||
|
||||
const lastBackupDescription = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and synced to ${lastBackup.agent_ids?.length} locations.`;
|
||||
const nextBackupDescription = this._nextBackupDescription(
|
||||
this.config.schedule.state
|
||||
);
|
||||
|
||||
const lastAttempt = this.config.last_attempted_automatic_backup
|
||||
? new Date(this.config.last_attempted_automatic_backup)
|
||||
: undefined;
|
||||
|
||||
if (lastAttempt && lastAttempt > lastBackupDate) {
|
||||
const lastAttemptDescription = `The last automatic backup trigged ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`;
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading=${`Last automatic backup failed`}
|
||||
status="error"
|
||||
>
|
||||
<ul class="list">
|
||||
<li class="item">
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span>${lastAttemptDescription}</span>
|
||||
</li>
|
||||
<li class="item">
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span>${lastBackupDescription}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
|
||||
if (numberOfDays > 0) {
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading=${`No backup for ${numberOfDays} days`}
|
||||
status="warning"
|
||||
>
|
||||
<ul class="list">
|
||||
<li class="item">
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span>${lastBackupDescription}</span>
|
||||
</li>
|
||||
<li class="item">
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span>${nextBackupDescription}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
||||
<ul class="list">
|
||||
<li class="item">
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span>${lastBackupDescription}</span>
|
||||
</li>
|
||||
<li class="item">
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span>${nextBackupDescription}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 8px 24px 24px 24px;
|
||||
margin: 0;
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.25px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
flex: none;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border-top: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-backup-overview-summary": HaBackupOverviewBackups;
|
||||
}
|
||||
}
|
@ -82,6 +82,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _step?: Step;
|
||||
|
||||
@state() private _steps: Step[] = [];
|
||||
|
||||
@state() private _params?: BackupOnboardingDialogParams;
|
||||
|
||||
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
||||
@ -90,7 +92,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
|
||||
public showDialog(params: BackupOnboardingDialogParams): void {
|
||||
this._params = params;
|
||||
this._step = STEPS[0];
|
||||
this._steps = params.showIntro ? STEPS.concat() : STEPS.slice(1);
|
||||
this._step = this._steps[0];
|
||||
this._config = RECOMMENDED_CONFIG;
|
||||
|
||||
// Enable local location by default
|
||||
@ -117,6 +120,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
}
|
||||
this._opened = false;
|
||||
this._step = undefined;
|
||||
this._steps = [];
|
||||
this._config = undefined;
|
||||
this._params = undefined;
|
||||
}
|
||||
@ -158,19 +162,19 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
private _previousStep() {
|
||||
const index = STEPS.indexOf(this._step!);
|
||||
const index = this._steps.indexOf(this._step!);
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
this._step = STEPS[index - 1];
|
||||
this._step = this._steps[index - 1];
|
||||
}
|
||||
|
||||
private _nextStep() {
|
||||
const index = STEPS.indexOf(this._step!);
|
||||
if (index === STEPS.length - 1) {
|
||||
const index = this._steps.indexOf(this._step!);
|
||||
if (index === this._steps.length - 1) {
|
||||
return;
|
||||
}
|
||||
this._step = STEPS[index + 1];
|
||||
this._step = this._steps[index + 1];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@ -178,8 +182,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const isLastStep = this._step === STEPS[STEPS.length - 1];
|
||||
const isFirstStep = this._step === STEPS[0];
|
||||
const isLastStep = this._step === this._steps[this._steps.length - 1];
|
||||
const isFirstStep = this._step === this._steps[0];
|
||||
|
||||
return html`
|
||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mdiClose, mdiCog, mdiPencil } from "@mdi/js";
|
||||
import { mdiCalendarSync, mdiClose, mdiGestureTap } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@ -7,6 +7,7 @@ import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-next";
|
||||
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-svg-icon";
|
||||
@ -14,7 +15,6 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { NewBackupDialogParams } from "./show-dialog-new-backup";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
|
||||
@customElement("ha-dialog-new-backup")
|
||||
class DialogNewBackup extends LitElement implements HassDialog {
|
||||
@ -75,7 +75,7 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
||||
type="button"
|
||||
.disabled=${!this._params.config.create_backup.password}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||
<span slot="headline">Automatic backup</span>
|
||||
<span slot="supporting-text">
|
||||
Create a backup with the data and locations you have configured.
|
||||
@ -83,7 +83,7 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item @click=${this._manual} type="button">
|
||||
<ha-svg-icon slot="start" .path=${mdiPencil}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||
<span slot="headline">Manual backup</span>
|
||||
<span slot="supporting-text">
|
||||
Select data and locations for a manual backup.
|
||||
|
@ -4,6 +4,7 @@ import type { CloudStatus } from "../../../../data/cloud";
|
||||
export interface BackupOnboardingDialogParams {
|
||||
submit?: (value: boolean) => void;
|
||||
cancel?: () => void;
|
||||
showIntro?: boolean;
|
||||
cloudStatus: CloudStatus;
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,14 @@ import {
|
||||
mdiPlus,
|
||||
mdiUpload,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
@ -36,11 +36,8 @@ import "../../../components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||
import {
|
||||
compareAgents,
|
||||
computeBackupAgentName,
|
||||
deleteBackup,
|
||||
fetchBackupConfig,
|
||||
fetchBackupInfo,
|
||||
generateBackup,
|
||||
generateBackupWithAutomaticSettings,
|
||||
getBackupDownloadUrl,
|
||||
@ -48,10 +45,6 @@ import {
|
||||
isLocalAgent,
|
||||
} from "../../../data/backup";
|
||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||
import {
|
||||
DEFAULT_MANAGER_STATE,
|
||||
subscribeBackupEvents,
|
||||
} from "../../../data/backup_manager";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import {
|
||||
@ -66,7 +59,6 @@ import type { HomeAssistant, Route } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { bytesToString } from "../../../util/bytes-to-string";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
||||
@ -89,14 +81,14 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
|
||||
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||
|
||||
@state() private _backups: BackupContent[] = [];
|
||||
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||
|
||||
@property({ attribute: false }) public config?: BackupConfig;
|
||||
|
||||
@state() private _selected: string[] = [];
|
||||
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
@storage({ key: "backups-table-grouping", state: false, subscribe: false })
|
||||
private _activeGrouping?: string = "formatted_type";
|
||||
|
||||
@ -107,8 +99,6 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
private _activeCollapsed: string[] = [];
|
||||
|
||||
private _subscribed?: Promise<() => void>;
|
||||
|
||||
@query("hass-tabs-subpage-data-table", true)
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
|
||||
@ -251,7 +241,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const backupInProgress =
|
||||
"state" in this._manager && this._manager.state === "in_progress";
|
||||
"state" in this.manager && this.manager.state === "in_progress";
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
@ -278,7 +268,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
.route=${this.route}
|
||||
@row-click=${this._showBackupDetails}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._data(this._backups)}
|
||||
.data=${this._data(this.backups)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.panel.config.backup.picker.search"
|
||||
@ -351,82 +341,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _unsubscribeEvents() {
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) => unsub());
|
||||
this._subscribed = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async _subscribeEvents() {
|
||||
this._unsubscribeEvents();
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._subscribed = subscribeBackupEvents(this.hass!, (event) => {
|
||||
this._manager = event;
|
||||
if ("state" in event) {
|
||||
if (event.state === "completed" || event.state === "failed") {
|
||||
this._fetchBackupInfo();
|
||||
}
|
||||
if (event.state === "failed") {
|
||||
let message = "";
|
||||
switch (this._manager.manager_state) {
|
||||
case "create_backup":
|
||||
message = "Failed to create backup";
|
||||
break;
|
||||
case "restore_backup":
|
||||
message = "Failed to restore backup";
|
||||
break;
|
||||
case "receive_backup":
|
||||
message = "Failed to upload backup";
|
||||
break;
|
||||
}
|
||||
if (message) {
|
||||
showToast(this, { message });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchBackupInfo();
|
||||
this._subscribeEvents();
|
||||
this._fetchBackupConfig();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._fetchBackupInfo();
|
||||
this._subscribeEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribeEvents();
|
||||
}
|
||||
|
||||
private async _fetchBackupInfo() {
|
||||
const info = await fetchBackupInfo(this.hass);
|
||||
this._backups = info.backups.map((backup) => ({
|
||||
...backup,
|
||||
agent_ids: backup.agent_ids?.sort(compareAgents),
|
||||
failed_agent_ids: backup.failed_agent_ids?.sort(compareAgents),
|
||||
}));
|
||||
}
|
||||
|
||||
private async _fetchBackupConfig() {
|
||||
const { config } = await fetchBackupConfig(this.hass);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private get _needsOnboarding() {
|
||||
return !this._config?.create_backup.password;
|
||||
return !this.config?.create_backup.password;
|
||||
}
|
||||
|
||||
private async _uploadBackup(ev) {
|
||||
@ -438,7 +354,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _newBackup(): Promise<void> {
|
||||
const config = this._config!;
|
||||
const config = this.config!;
|
||||
|
||||
const type = await showNewBackupDialog(this, { config });
|
||||
|
||||
@ -454,12 +370,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
await generateBackup(this.hass, params);
|
||||
await this._fetchBackupInfo();
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
return;
|
||||
}
|
||||
if (type === "automatic") {
|
||||
await generateBackupWithAutomaticSettings(this.hass);
|
||||
await this._fetchBackupInfo();
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
}
|
||||
}
|
||||
|
||||
@ -490,7 +406,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
await deleteBackup(this.hass, backup.backup_id);
|
||||
this._fetchBackupInfo();
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
}
|
||||
|
||||
private async _deleteSelected() {
|
||||
@ -516,7 +432,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this._fetchBackupInfo();
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
|
@ -1,138 +0,0 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import type { BackupAgent } from "../../../data/backup";
|
||||
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 {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _agents: BackupAgent[] = [];
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchAgents();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.backup.caption")}
|
||||
>
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<h2 class="title">Locations</h2>
|
||||
<p class="description">
|
||||
To keep your data safe it is recommended your backups is at least
|
||||
on two different locations and one of them is off-site.
|
||||
</p>
|
||||
</div>
|
||||
<ha-card class="agents">
|
||||
<div class="card-content">
|
||||
${this._agents.length > 0
|
||||
? html`
|
||||
<ha-md-list>
|
||||
${this._agents.map((agent) => {
|
||||
const [domain, name] = agent.agent_id.split(".");
|
||||
const domainName = domainToName(
|
||||
this.hass.localize,
|
||||
domain
|
||||
);
|
||||
return html`
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/backup/locations/${agent.agent_id}"
|
||||
>
|
||||
<img
|
||||
.src=${brandsUrl({
|
||||
domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
alt=""
|
||||
slot="start"
|
||||
/>
|
||||
<div slot="headline">${domainName}: ${name}</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
})}
|
||||
</ha-md-list>
|
||||
`
|
||||
: html`<p>No sync agents configured</p>`}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchAgents() {
|
||||
const data = await fetchBackupAgentsInfo(this.hass);
|
||||
this._agents = data.agents;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
margin: 0 auto;
|
||||
gap: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header .title {
|
||||
font-size: 22px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: var(--primary-text-color);
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header .description {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.25px;
|
||||
color: var(--secondary-text-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
}
|
||||
ha-md-list-item img {
|
||||
width: 48px;
|
||||
}
|
||||
.card-content {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-backup-locations": HaConfigBackupLocations;
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
@ -13,25 +14,24 @@ import "../../../components/ha-icon-overflow-menu";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
fetchBackupConfig,
|
||||
fetchBackupInfo,
|
||||
generateBackup,
|
||||
generateBackupWithAutomaticSettings,
|
||||
type BackupConfig,
|
||||
type BackupContent,
|
||||
} from "../../../data/backup";
|
||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||
import { DEFAULT_MANAGER_STATE } from "../../../data/backup_manager";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import "./components/ha-backup-summary-card";
|
||||
import "./components/ha-backup-summary-progress";
|
||||
import "./components/ha-backup-summary-status";
|
||||
import "./components/overview/ha-backup-overview-backups";
|
||||
import "./components/overview/ha-backup-overview-onboarding";
|
||||
import "./components/overview/ha-backup-overview-progress";
|
||||
import "./components/overview/ha-backup-overview-settings";
|
||||
import "./components/overview/ha-backup-overview-summary";
|
||||
import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding";
|
||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||
@ -47,27 +47,13 @@ class HaConfigBackupOverview extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
|
||||
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||
|
||||
@state() private _backups: BackupContent[] = [];
|
||||
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||
|
||||
@state() private _fetching = false;
|
||||
@property({ attribute: false }) public fetching = false;
|
||||
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchBackupInfo();
|
||||
this._fetchBackupConfig();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._fetchBackupInfo();
|
||||
this._fetchBackupConfig();
|
||||
}
|
||||
}
|
||||
@property({ attribute: false }) public config?: BackupConfig;
|
||||
|
||||
private async _uploadBackup(ev) {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
@ -77,40 +63,36 @@ class HaConfigBackupOverview extends LitElement {
|
||||
await showUploadBackupDialog(this, {});
|
||||
}
|
||||
|
||||
private async _setupAutomaticBackup() {
|
||||
private _handleOnboardingButtonClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this._setupAutomaticBackup(false);
|
||||
}
|
||||
|
||||
private async _setupAutomaticBackup(showIntro: boolean) {
|
||||
const success = await showBackupOnboardingDialog(this, {
|
||||
cloudStatus: this.cloudStatus,
|
||||
showIntro: showIntro,
|
||||
});
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fetchBackupConfig();
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
await generateBackupWithAutomaticSettings(this.hass);
|
||||
await this._fetchBackupInfo();
|
||||
}
|
||||
|
||||
private async _fetchBackupInfo() {
|
||||
const info = await fetchBackupInfo(this.hass);
|
||||
this._backups = info.backups;
|
||||
}
|
||||
|
||||
private async _fetchBackupConfig() {
|
||||
const { config } = await fetchBackupConfig(this.hass);
|
||||
this._config = config;
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
}
|
||||
|
||||
private async _newBackup(): Promise<void> {
|
||||
if (this._needsOnboarding) {
|
||||
this._setupAutomaticBackup();
|
||||
this._setupAutomaticBackup(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._config) {
|
||||
if (!this.config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = this._config;
|
||||
const config = this.config;
|
||||
|
||||
const type = await showNewBackupDialog(this, { config });
|
||||
|
||||
@ -126,22 +108,22 @@ class HaConfigBackupOverview extends LitElement {
|
||||
}
|
||||
|
||||
await generateBackup(this.hass, params);
|
||||
await this._fetchBackupInfo();
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
return;
|
||||
}
|
||||
if (type === "automatic") {
|
||||
await generateBackupWithAutomaticSettings(this.hass);
|
||||
await this._fetchBackupInfo();
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
}
|
||||
}
|
||||
|
||||
private get _needsOnboarding() {
|
||||
return !this._config?.create_backup.password;
|
||||
return !this.config?.create_backup.password;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const backupInProgress =
|
||||
"state" in this._manager && this._manager.state === "in_progress";
|
||||
"state" in this.manager && this.manager.state === "in_progress";
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
@ -167,57 +149,50 @@ class HaConfigBackupOverview extends LitElement {
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="content">
|
||||
${this._fetching
|
||||
${backupInProgress
|
||||
? html`
|
||||
<ha-backup-summary-card
|
||||
heading="Loading backups"
|
||||
description="Your backup information is being retrieved."
|
||||
status="loading"
|
||||
<ha-backup-overview-progress
|
||||
.hass=${this.hass}
|
||||
.manager=${this.manager}
|
||||
>
|
||||
</ha-backup-summary-card>
|
||||
</ha-backup-overview-progress>
|
||||
`
|
||||
: backupInProgress
|
||||
: this.fetching
|
||||
? html`
|
||||
<ha-backup-summary-progress
|
||||
.hass=${this.hass}
|
||||
.manager=${this._manager}
|
||||
<ha-backup-summary-card
|
||||
heading="Loading backups"
|
||||
description="Your backup information is being retrieved."
|
||||
status="loading"
|
||||
>
|
||||
</ha-backup-summary-progress>
|
||||
</ha-backup-summary-card>
|
||||
`
|
||||
: this._needsOnboarding
|
||||
? html`
|
||||
<ha-backup-summary-card
|
||||
heading="Configure automatic backups"
|
||||
description="Have a one-click backup automation with selected data and locations."
|
||||
has-action
|
||||
status="info"
|
||||
<ha-backup-overview-onboarding
|
||||
.hass=${this.hass}
|
||||
@button-click=${this._handleOnboardingButtonClick}
|
||||
>
|
||||
<ha-button
|
||||
slot="action"
|
||||
@click=${this._setupAutomaticBackup}
|
||||
>
|
||||
Set up automatic backups
|
||||
</ha-button>
|
||||
</ha-backup-summary-card>
|
||||
</ha-backup-overview-onboarding>
|
||||
`
|
||||
: html`
|
||||
<ha-backup-summary-status
|
||||
<ha-backup-overview-summary
|
||||
.hass=${this.hass}
|
||||
.backups=${this._backups}
|
||||
.backups=${this.backups}
|
||||
.config=${this.config}
|
||||
>
|
||||
</ha-backup-summary-status>
|
||||
</ha-backup-overview-summary>
|
||||
`}
|
||||
|
||||
<ha-backup-overview-backups
|
||||
.hass=${this.hass}
|
||||
.backups=${this._backups}
|
||||
.backups=${this.backups}
|
||||
></ha-backup-overview-backups>
|
||||
|
||||
${!this._needsOnboarding
|
||||
? html`
|
||||
<ha-backup-overview-settings
|
||||
.hass=${this.hass}
|
||||
.config=${this._config!}
|
||||
.config=${this.config!}
|
||||
></ha-backup-overview-settings>
|
||||
`
|
||||
: nothing}
|
||||
@ -258,6 +233,10 @@ class HaConfigBackupOverview extends LitElement {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
ha-fab[disabled] {
|
||||
--mdc-theme-secondary: var(--disabled-text-color) !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -1,17 +1,14 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-password-field";
|
||||
import "../../../components/ha-settings-row";
|
||||
import type { BackupConfig } from "../../../data/backup";
|
||||
import {
|
||||
BackupScheduleState,
|
||||
fetchBackupConfig,
|
||||
updateBackupConfig,
|
||||
} from "../../../data/backup";
|
||||
import { updateBackupConfig } from "../../../data/backup";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@ -22,27 +19,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";
|
||||
|
||||
const INITIAL_BACKUP_CONFIG: BackupConfig = {
|
||||
create_backup: {
|
||||
agent_ids: [],
|
||||
include_folders: [],
|
||||
include_database: true,
|
||||
include_addons: [],
|
||||
include_all_addons: true,
|
||||
password: null,
|
||||
name: null,
|
||||
},
|
||||
retention: {
|
||||
copies: 3,
|
||||
days: null,
|
||||
},
|
||||
schedule: {
|
||||
state: BackupScheduleState.DAILY,
|
||||
},
|
||||
last_attempted_automatic_backup: null,
|
||||
last_completed_automatic_backup: null,
|
||||
};
|
||||
|
||||
@customElement("ha-config-backup-settings")
|
||||
class HaConfigBackupSettings extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -51,22 +27,19 @@ class HaConfigBackupSettings extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _backupConfig: BackupConfig = INITIAL_BACKUP_CONFIG;
|
||||
@property({ attribute: false }) public config?: BackupConfig;
|
||||
|
||||
protected willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this._fetchData();
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("config") && !this._config) {
|
||||
this._config = this.config;
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
const { config } = await fetchBackupConfig(this.hass);
|
||||
this._backupConfig = config;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._backupConfig) {
|
||||
if (!this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@ -87,7 +60,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
</p>
|
||||
<ha-backup-config-schedule
|
||||
.hass=${this.hass}
|
||||
.value=${this._backupConfig}
|
||||
.value=${this._config}
|
||||
@value-changed=${this._scheduleConfigChanged}
|
||||
></ha-backup-config-schedule>
|
||||
</div>
|
||||
@ -113,7 +86,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
</p>
|
||||
<ha-backup-config-agents
|
||||
.hass=${this.hass}
|
||||
.value=${this._backupConfig.create_backup.agent_ids}
|
||||
.value=${this._config.create_backup.agent_ids}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
@value-changed=${this._agentsConfigChanged}
|
||||
></ha-backup-config-agents>
|
||||
@ -130,7 +103,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
</p>
|
||||
<ha-backup-config-encryption-key
|
||||
.hass=${this.hass}
|
||||
.value=${this._backupConfig.create_backup.password}
|
||||
.value=${this._config.create_backup.password}
|
||||
@value-changed=${this._encryptionKeyChanged}
|
||||
></ha-backup-config-encryption-key>
|
||||
</div>
|
||||
@ -142,8 +115,8 @@ class HaConfigBackupSettings extends LitElement {
|
||||
|
||||
private _scheduleConfigChanged(ev) {
|
||||
const value = ev.detail.value as BackupConfigSchedule;
|
||||
this._backupConfig = {
|
||||
...this._backupConfig,
|
||||
this._config = {
|
||||
...this._config!,
|
||||
schedule: value.schedule,
|
||||
retention: value.retention,
|
||||
};
|
||||
@ -156,7 +129,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
include_all_addons,
|
||||
include_database,
|
||||
include_folders,
|
||||
} = this._backupConfig.create_backup;
|
||||
} = this._config!.create_backup;
|
||||
|
||||
return {
|
||||
include_homeassistant: true,
|
||||
@ -169,10 +142,10 @@ class HaConfigBackupSettings extends LitElement {
|
||||
|
||||
private _dataConfigChanged(ev) {
|
||||
const data = ev.detail.value as BackupConfigData;
|
||||
this._backupConfig = {
|
||||
...this._backupConfig,
|
||||
this._config = {
|
||||
...this._config!,
|
||||
create_backup: {
|
||||
...this._backupConfig.create_backup,
|
||||
...this.config!.create_backup,
|
||||
include_database: data.include_database,
|
||||
include_folders: data.include_folders || null,
|
||||
include_all_addons: data.include_all_addons,
|
||||
@ -184,10 +157,10 @@ class HaConfigBackupSettings extends LitElement {
|
||||
|
||||
private _agentsConfigChanged(ev) {
|
||||
const agents = ev.detail.value as string[];
|
||||
this._backupConfig = {
|
||||
...this._backupConfig,
|
||||
this._config = {
|
||||
...this._config!,
|
||||
create_backup: {
|
||||
...this._backupConfig.create_backup,
|
||||
...this._config!.create_backup,
|
||||
agent_ids: agents,
|
||||
},
|
||||
};
|
||||
@ -196,10 +169,10 @@ class HaConfigBackupSettings extends LitElement {
|
||||
|
||||
private _encryptionKeyChanged(ev) {
|
||||
const password = ev.detail.value as string;
|
||||
this._backupConfig = {
|
||||
...this._backupConfig,
|
||||
this._config = {
|
||||
...this._config!,
|
||||
create_backup: {
|
||||
...this._backupConfig.create_backup,
|
||||
...this._config!.create_backup,
|
||||
password: password,
|
||||
},
|
||||
};
|
||||
@ -211,16 +184,17 @@ class HaConfigBackupSettings extends LitElement {
|
||||
private async _save() {
|
||||
await updateBackupConfig(this.hass, {
|
||||
create_backup: {
|
||||
agent_ids: this._backupConfig.create_backup.agent_ids,
|
||||
include_folders: this._backupConfig.create_backup.include_folders ?? [],
|
||||
include_database: this._backupConfig.create_backup.include_database,
|
||||
include_addons: this._backupConfig.create_backup.include_addons ?? [],
|
||||
include_all_addons: this._backupConfig.create_backup.include_all_addons,
|
||||
password: this._backupConfig.create_backup.password,
|
||||
agent_ids: this._config!.create_backup.agent_ids,
|
||||
include_folders: this._config!.create_backup.include_folders ?? [],
|
||||
include_database: this._config!.create_backup.include_database,
|
||||
include_addons: this._config!.create_backup.include_addons ?? [],
|
||||
include_all_addons: this._config!.create_backup.include_all_addons,
|
||||
password: this._config!.create_backup.password,
|
||||
},
|
||||
retention: this._backupConfig.retention,
|
||||
schedule: this._backupConfig.schedule.state,
|
||||
retention: this._config!.retention,
|
||||
schedule: this._config!.schedule.state,
|
||||
});
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
@ -233,14 +207,6 @@ class HaConfigBackupSettings extends LitElement {
|
||||
flex-direction: column;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-settings-row {
|
||||
--settings-row-prefix-display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
ha-settings-row > ha-svg-icon {
|
||||
align-self: center;
|
||||
margin-inline-end: 16px;
|
||||
}
|
||||
.alert {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
@ -1,21 +1,89 @@
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||
import {
|
||||
DEFAULT_MANAGER_STATE,
|
||||
subscribeBackupEvents,
|
||||
} from "../../../data/backup_manager";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import type { RouterOptions } from "../../../layouts/hass-router-page";
|
||||
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./ha-config-backup-overview";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "./ha-config-backup-backups";
|
||||
import "./ha-config-backup-overview";
|
||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||
import {
|
||||
compareAgents,
|
||||
fetchBackupConfig,
|
||||
fetchBackupInfo,
|
||||
} from "../../../data/backup";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"ha-refresh-backup-info": undefined;
|
||||
"ha-refresh-backup-config": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-config-backup")
|
||||
class HaConfigBackup extends HassRouterPage {
|
||||
class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
|
||||
|
||||
@state() private _backups: BackupContent[] = [];
|
||||
|
||||
@state() private _fetching = false;
|
||||
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetching = true;
|
||||
Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally(
|
||||
() => {
|
||||
this._fetching = false;
|
||||
}
|
||||
);
|
||||
|
||||
this.addEventListener("ha-refresh-backup-info", () => {
|
||||
this._fetchBackupInfo();
|
||||
});
|
||||
this.addEventListener("ha-refresh-backup-config", () => {
|
||||
this._fetchBackupConfig();
|
||||
});
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._fetchBackupInfo();
|
||||
this._fetchBackupConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchBackupInfo() {
|
||||
const info = await fetchBackupInfo(this.hass);
|
||||
this._backups = info.backups.map((backup) => ({
|
||||
...backup,
|
||||
agent_ids: backup.agent_ids?.sort(compareAgents),
|
||||
failed_agent_ids: backup.failed_agent_ids?.sort(compareAgents),
|
||||
}));
|
||||
}
|
||||
|
||||
private async _fetchBackupConfig() {
|
||||
const { config } = await fetchBackupConfig(this.hass);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "overview",
|
||||
routes: {
|
||||
@ -31,10 +99,6 @@ class HaConfigBackup extends HassRouterPage {
|
||||
tag: "ha-config-backup-details",
|
||||
load: () => import("./ha-config-backup-details"),
|
||||
},
|
||||
locations: {
|
||||
tag: "ha-config-backup-locations",
|
||||
load: () => import("./ha-config-backup-locations"),
|
||||
},
|
||||
settings: {
|
||||
tag: "ha-config-backup-settings",
|
||||
load: () => import("./ha-config-backup-settings"),
|
||||
@ -47,7 +111,12 @@ class HaConfigBackup extends HassRouterPage {
|
||||
pageEl.route = this.routeTail;
|
||||
pageEl.narrow = this.narrow;
|
||||
pageEl.cloudStatus = this.cloudStatus;
|
||||
pageEl.manager = this._manager;
|
||||
pageEl.backups = this._backups;
|
||||
pageEl.config = this._config;
|
||||
pageEl.fetching = this._fetching;
|
||||
|
||||
pageEl.addEventListener("reload", () => {});
|
||||
if (
|
||||
(!changedProps || changedProps.has("route")) &&
|
||||
this._currentPage === "details"
|
||||
@ -55,6 +124,36 @@ class HaConfigBackup extends HassRouterPage {
|
||||
pageEl.backupId = this.routeTail.path.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
|
||||
return [
|
||||
subscribeBackupEvents(this.hass!, (event) => {
|
||||
this._manager = event;
|
||||
if ("state" in event) {
|
||||
if (event.state === "completed" || event.state === "failed") {
|
||||
this._fetchBackupInfo();
|
||||
}
|
||||
if (event.state === "failed") {
|
||||
let message = "";
|
||||
switch (this._manager.manager_state) {
|
||||
case "create_backup":
|
||||
message = "Failed to create backup";
|
||||
break;
|
||||
case "restore_backup":
|
||||
message = "Failed to restore backup";
|
||||
break;
|
||||
case "receive_backup":
|
||||
message = "Failed to upload backup";
|
||||
break;
|
||||
}
|
||||
if (message) {
|
||||
showToast(this, { message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
Loading…
x
Reference in New Issue
Block a user