Improve error handling in backup status banner (#23604)
* Improve error handling in backup status banner * Fix completion * Fix loading * Check attempt and completion date firstpull/23613/head
parent
2a2b6d33b8
commit
ffff7970f5
|
@ -31,14 +31,24 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
|
|
||||||
@property({ type: Boolean }) public fetching = false;
|
@property({ type: Boolean }) public fetching = false;
|
||||||
|
|
||||||
private _lastSuccessfulBackup = memoizeOne((backups: BackupContent[]) => {
|
private _sortedBackups = memoizeOne((backups: BackupContent[]) =>
|
||||||
const sortedBackups = backups
|
backups
|
||||||
.filter((backup) => backup.with_automatic_settings)
|
.filter((backup) => backup.with_automatic_settings)
|
||||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||||
|
);
|
||||||
|
|
||||||
|
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
||||||
|
const sortedBackups = this._sortedBackups(backups);
|
||||||
return sortedBackups[0] as BackupContent | undefined;
|
return sortedBackups[0] as BackupContent | undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private _lastUploadedBackup = memoizeOne((backups: BackupContent[]) => {
|
||||||
|
const sortedBackups = this._sortedBackups(backups);
|
||||||
|
return sortedBackups.find(
|
||||||
|
(backup) => backup.failed_agent_ids?.length === 0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
private _nextBackupDescription(schedule: BackupScheduleState) {
|
private _nextBackupDescription(schedule: BackupScheduleState) {
|
||||||
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
||||||
|
|
||||||
|
@ -65,6 +75,8 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
if (this.fetching) {
|
if (this.fetching) {
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card heading="Loading backups" status="loading">
|
<ha-backup-summary-card heading="Loading backups" status="loading">
|
||||||
|
@ -82,24 +94,28 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastSuccessfulBackup = this._lastSuccessfulBackup(this.backups);
|
const lastBackup = this._lastBackup(this.backups);
|
||||||
|
|
||||||
const lastAttempt = this.config.last_attempted_automatic_backup
|
const nextBackupDescription = this._nextBackupDescription(
|
||||||
|
this.config.schedule.state
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastAttemptDate = this.config.last_attempted_automatic_backup
|
||||||
? new Date(this.config.last_attempted_automatic_backup)
|
? new Date(this.config.last_attempted_automatic_backup)
|
||||||
: undefined;
|
: new Date(0);
|
||||||
|
|
||||||
const lastCompletedBackupDate = this.config.last_completed_automatic_backup
|
const lastCompletedDate = this.config.last_completed_automatic_backup
|
||||||
? new Date(this.config.last_completed_automatic_backup)
|
? new Date(this.config.last_completed_automatic_backup)
|
||||||
: undefined;
|
: new Date(0);
|
||||||
|
|
||||||
const now = new Date();
|
// If last attempt is after last completed backup, show error
|
||||||
|
if (lastAttemptDate > lastCompletedDate) {
|
||||||
|
const description = `The last automatic backup triggered ${relativeTime(lastAttemptDate, this.hass.locale, now, true)} wasn't successful.`;
|
||||||
|
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||||
|
const secondaryDescription = lastUploadedBackup
|
||||||
|
? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.`
|
||||||
|
: nextBackupDescription;
|
||||||
|
|
||||||
const lastBackupDescription = lastSuccessfulBackup
|
|
||||||
? `Last successful backup ${relativeTime(new Date(lastSuccessfulBackup.date), this.hass.locale, now, true)} and stored in ${lastSuccessfulBackup.agent_ids?.length} locations.`
|
|
||||||
: "You have no successful backups.";
|
|
||||||
|
|
||||||
if (lastAttempt && lastAttempt > (lastCompletedBackupDate || 0)) {
|
|
||||||
const lastAttemptDescription = `The last automatic backup triggered ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`;
|
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-summary-card
|
||||||
heading="Last automatic backup failed"
|
heading="Last automatic backup failed"
|
||||||
|
@ -108,29 +124,30 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
<span slot="headline">${lastAttemptDescription}</span>
|
<span slot="headline">${description}</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
<span slot="headline">${lastBackupDescription}</span>
|
<span slot="headline">${secondaryDescription}</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextBackupDescription = this._nextBackupDescription(
|
// If no backups yet, show warning
|
||||||
this.config.schedule.state
|
if (!lastBackup) {
|
||||||
);
|
const description = "You have no automatic backups yet.";
|
||||||
|
|
||||||
if (!lastSuccessfulBackup) {
|
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-summary-card
|
||||||
heading="No automatic backup available"
|
heading="No automatic backup available"
|
||||||
description="You have no automatic backups yet."
|
|
||||||
status="warning"
|
status="warning"
|
||||||
>
|
>
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
|
<ha-md-list-item>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
|
<span slot="headline">${description}</span>
|
||||||
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
<span slot="headline">${nextBackupDescription}</span>
|
<span slot="headline">${nextBackupDescription}</span>
|
||||||
|
@ -140,10 +157,41 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastBackupDate = new Date(lastBackup.date);
|
||||||
|
|
||||||
|
// If last backup
|
||||||
|
if (lastBackup.failed_agent_ids?.length) {
|
||||||
|
const description = `The last automatic backup created ${relativeTime(lastBackupDate, this.hass.locale, now, true)} wasn't stored in all locations.`;
|
||||||
|
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||||
|
const secondaryDescription = lastUploadedBackup
|
||||||
|
? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.`
|
||||||
|
: nextBackupDescription;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card
|
||||||
|
heading="Last automatic backup failed"
|
||||||
|
status="error"
|
||||||
|
>
|
||||||
|
<ha-md-list>
|
||||||
|
<ha-md-list-item>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
|
<span slot="headline">${description}</span>
|
||||||
|
</ha-md-list-item>
|
||||||
|
<ha-md-list-item>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
<span slot="headline">${secondaryDescription}</span>
|
||||||
|
</ha-md-list-item>
|
||||||
|
</ha-md-list>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const description = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored in ${lastBackup.agent_ids?.length} locations.`;
|
||||||
|
|
||||||
const numberOfDays = differenceInDays(
|
const numberOfDays = differenceInDays(
|
||||||
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
|
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
|
||||||
addHours(now, -OVERDUE_MARGIN_HOURS),
|
addHours(now, -OVERDUE_MARGIN_HOURS),
|
||||||
new Date(lastSuccessfulBackup.date)
|
lastBackupDate
|
||||||
);
|
);
|
||||||
|
|
||||||
const isOverdue =
|
const isOverdue =
|
||||||
|
@ -160,7 +208,7 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
<span slot="headline">${lastBackupDescription}</span>
|
<span slot="headline">${description}</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
@ -170,12 +218,13 @@ class HaBackupOverviewBackups extends LitElement {
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
<span slot="headline">${lastBackupDescription}</span>
|
<span slot="headline">${description}</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||||
|
import {
|
||||||
|
compareAgents,
|
||||||
|
fetchBackupConfig,
|
||||||
|
fetchBackupInfo,
|
||||||
|
} from "../../../data/backup";
|
||||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||||
import {
|
import {
|
||||||
DEFAULT_MANAGER_STATE,
|
DEFAULT_MANAGER_STATE,
|
||||||
|
@ -15,12 +21,6 @@ import type { HomeAssistant } from "../../../types";
|
||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import "./ha-config-backup-backups";
|
import "./ha-config-backup-backups";
|
||||||
import "./ha-config-backup-overview";
|
import "./ha-config-backup-overview";
|
||||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
|
||||||
import {
|
|
||||||
compareAgents,
|
|
||||||
fetchBackupConfig,
|
|
||||||
fetchBackupInfo,
|
|
||||||
} from "../../../data/backup";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
|
@ -47,13 +47,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._fetching = true;
|
this._fetchAll();
|
||||||
Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally(
|
|
||||||
() => {
|
|
||||||
this._fetching = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addEventListener("ha-refresh-backup-info", () => {
|
this.addEventListener("ha-refresh-backup-info", () => {
|
||||||
this._fetchBackupInfo();
|
this._fetchBackupInfo();
|
||||||
});
|
});
|
||||||
|
@ -62,6 +56,15 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _fetchAll() {
|
||||||
|
this._fetching = true;
|
||||||
|
Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally(
|
||||||
|
() => {
|
||||||
|
this._fetching = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this.hasUpdated) {
|
if (this.hasUpdated) {
|
||||||
|
@ -128,11 +131,16 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||||
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
|
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
|
||||||
return [
|
return [
|
||||||
subscribeBackupEvents(this.hass!, (event) => {
|
subscribeBackupEvents(this.hass!, (event) => {
|
||||||
|
const curState = this._manager.manager_state;
|
||||||
|
|
||||||
this._manager = event;
|
this._manager = event;
|
||||||
if ("state" in event) {
|
if (
|
||||||
if (event.state === "completed" || event.state === "failed") {
|
event.manager_state === "idle" &&
|
||||||
this._fetchBackupInfo();
|
event.manager_state !== curState
|
||||||
|
) {
|
||||||
|
this._fetchAll();
|
||||||
}
|
}
|
||||||
|
if ("state" in event) {
|
||||||
if (event.state === "failed") {
|
if (event.state === "failed") {
|
||||||
let message = "";
|
let message = "";
|
||||||
switch (this._manager.manager_state) {
|
switch (this._manager.manager_state) {
|
||||||
|
|
Loading…
Reference in New Issue