Backup status banner improvement (#23400)

* Improve overdue backups and don't consider backup with failed agents as success

* Improve margin and loader

* Improve margin

* Show agent name if only one off site location is configured
This commit is contained in:
Paul Bottein 2024-12-23 15:06:36 +01:00 committed by GitHub
parent 76cb9ce807
commit 5fb384ad31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 141 additions and 160 deletions

View File

@ -75,6 +75,7 @@ class HaBackupSummaryCard extends LitElement {
row-gap: 8px;
align-items: center;
padding: 16px;
padding-bottom: 8px;
width: 100%;
box-sizing: border-box;
}
@ -145,10 +146,6 @@ class HaBackupSummaryCard extends LitElement {
}
@media all and (max-width: 550px) {
.summary {
flex-wrap: wrap;
padding: 8px;
}
.action {
width: 100%;
display: flex;

View File

@ -1,84 +0,0 @@
import { differenceInDays } from "date-fns";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { formatShortDateTime } from "../../../../common/datetime/format_date_time";
import type { BackupContent } from "../../../../data/backup";
import type { ManagerStateEvent } from "../../../../data/backup_manager";
import type { HomeAssistant } from "../../../../types";
import "./ha-backup-summary-card";
@customElement("ha-backup-summary-status")
export class HaBackupSummaryProgress extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public manager!: ManagerStateEvent;
@property({ attribute: false }) public backups!: BackupContent[];
@property({ type: Boolean, attribute: "has-action" })
public hasAction = false;
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
const sortedBackups = backups
// eslint-disable-next-line arrow-body-style
.filter((backup) => {
// TODO : only show backups with default flag
return backup.with_automatic_settings;
})
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return sortedBackups[0] as BackupContent | undefined;
});
protected render() {
const lastBackup = this._lastBackup(this.backups);
if (!lastBackup) {
return html`
<ha-backup-summary-card
heading="No backup available"
description="You have not created any backups yet."
.hasAction=${this.hasAction}
status="warning"
>
<slot name="action" slot="action"></slot>
</ha-backup-summary-card>
`;
}
const lastBackupDate = new Date(lastBackup.date);
const numberOfDays = differenceInDays(new Date(), lastBackupDate);
// TODO : Improve time format
const description = `Last successful backup ${formatShortDateTime(lastBackupDate, this.hass.locale, this.hass.config)} and synced to ${lastBackup.agent_ids?.length} locations`;
if (numberOfDays > 8) {
return html`
<ha-backup-summary-card
heading=${`No backup for ${numberOfDays} days`}
description=${description}
.hasAction=${this.hasAction}
status="warning"
>
<slot name="action" slot="action"></slot>
</ha-backup-summary-card>
`;
}
return html`
<ha-backup-summary-card
heading=${`Backed up`}
description=${description}
.hasAction=${this.hasAction}
status="success"
>
<slot name="action" slot="action"></slot>
</ha-backup-summary-card>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-backup-summary-status": HaBackupSummaryProgress;
}
}

View File

@ -109,9 +109,13 @@ class HaBackupOverviewBackups extends LitElement {
display: flex;
justify-content: flex-end;
}
.card-header {
padding-bottom: 8px;
}
.card-content {
padding-left: 0;
padding-right: 0;
padding-bottom: 0;
}
`,
];

View File

@ -10,7 +10,11 @@ import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-svg-icon";
import type { BackupConfig } from "../../../../../data/backup";
import { BackupScheduleState, isLocalAgent } from "../../../../../data/backup";
import {
BackupScheduleState,
computeBackupAgentName,
isLocalAgent,
} from "../../../../../data/backup";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
@ -88,6 +92,14 @@ class HaBackupBackupsSummary extends LitElement {
);
if (offsiteLocations.length) {
if (offsiteLocations.length === 1) {
const name = computeBackupAgentName(
this.hass.localize,
offsiteLocations[0],
offsiteLocations
);
return `Upload to ${name}`;
}
return `Upload to ${offsiteLocations.length} off-site locations`;
}
if (hasLocal) {
@ -184,9 +196,13 @@ class HaBackupBackupsSummary extends LitElement {
display: flex;
justify-content: flex-end;
}
.card-header {
padding-bottom: 8px;
}
.card-content {
padding-left: 0;
padding-right: 0;
padding-bottom: 0;
}
`,
];

View File

@ -1,5 +1,5 @@
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
import { differenceInDays, setHours, setMinutes } from "date-fns";
import { addHours, differenceInDays, setHours, setMinutes } from "date-fns";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
@ -17,6 +17,8 @@ import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import "../ha-backup-summary-card";
const OVERDUE_MARGIN_HOURS = 3;
@customElement("ha-backup-overview-summary")
class HaBackupOverviewBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -25,9 +27,14 @@ class HaBackupOverviewBackups extends LitElement {
@property({ attribute: false }) public config!: BackupConfig;
@property({ type: Boolean }) public fetching = false;
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
const sortedBackups = backups
.filter((backup) => backup.with_automatic_settings)
.filter(
(backup) =>
backup.with_automatic_settings && !backup.failed_agent_ids?.length
)
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return sortedBackups[0] as BackupContent | undefined;
@ -60,6 +67,23 @@ class HaBackupOverviewBackups extends LitElement {
}
protected render() {
if (this.fetching) {
return html`
<ha-backup-summary-card heading="Loading backups" status="loading">
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span slot="headline" class="skeleton"></span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span slot="headline" class="skeleton"></span>
</ha-md-list-item>
</ha-md-list>
</ha-backup-summary-card>
`;
}
const lastBackup = this._lastBackup(this.backups);
if (!lastBackup) {
@ -75,10 +99,9 @@ class HaBackupOverviewBackups extends LitElement {
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 lastBackupDescription = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored to ${lastBackup.agent_ids?.length} locations.`;
const nextBackupDescription = this._nextBackupDescription(
this.config.schedule.state
);
@ -94,51 +117,62 @@ class HaBackupOverviewBackups extends LitElement {
heading=${`Last automatic backup failed`}
status="error"
>
<ul class="list">
<li class="item">
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span>${lastAttemptDescription}</span>
</li>
<li class="item">
<span slot="headline">${lastAttemptDescription}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span>${lastBackupDescription}</span>
</li>
</ul>
<span slot="headline">${lastBackupDescription}</span>
</ha-md-list-item>
</ha-md-list>
</ha-backup-summary-card>
`;
}
if (numberOfDays > 0) {
const numberOfDays = differenceInDays(
// 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),
lastBackupDate
);
const isOverdue =
(numberOfDays >= 1 &&
this.config.schedule.state === BackupScheduleState.DAILY) ||
numberOfDays >= 7;
if (isOverdue) {
return html`
<ha-backup-summary-card
heading=${`No backup for ${numberOfDays} days`}
status="warning"
>
<ul class="list">
<li class="item">
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span>${lastBackupDescription}</span>
</li>
<li class="item">
<span slot="headline">${lastBackupDescription}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span>${nextBackupDescription}</span>
</li>
</ul>
<span slot="headline">${nextBackupDescription}</span>
</ha-md-list-item>
</ha-md-list>
</ha-backup-summary-card>
`;
}
return html`
<ha-backup-summary-card heading=${`Backed up`} status="success">
<ul class="list">
<li class="item">
<ha-md-list>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
<span>${lastBackupDescription}</span>
</li>
<li class="item">
<span slot="headline">${lastBackupDescription}</span>
</ha-md-list-item>
<ha-md-list-item>
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
<span>${nextBackupDescription}</span>
</li>
</ul>
<span slot="headline">${nextBackupDescription}</span>
</ha-md-list-item>
</ha-md-list>
</ha-backup-summary-card>
`;
}
@ -156,25 +190,6 @@ class HaBackupOverviewBackups extends LitElement {
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;
}
@ -183,6 +198,42 @@ class HaBackupOverviewBackups extends LitElement {
justify-content: flex-end;
border-top: none;
}
ha-md-list {
background: none;
}
ha-md-list-item {
--md-list-item-top-space: 8px;
--md-list-item-bottom-space: 8px;
--md-list-item-one-line-container-height: 40x;
}
span.skeleton {
position: relative;
display: block;
width: 160px;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-name: loading;
animation-timing-function: linear;
animation-duration: 1.2s;
border-radius: 4px;
height: 20px;
background: linear-gradient(
to right,
rgb(247, 249, 250) 8%,
rgb(235, 238, 240) 18%,
rgb(247, 249, 250) 33%
)
0% 0% / 936px 104px;
}
@keyframes loading {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
`,
];
}

View File

@ -27,7 +27,6 @@ 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-status";
import "./components/overview/ha-backup-overview-backups";
import "./components/overview/ha-backup-overview-onboarding";
import "./components/overview/ha-backup-overview-progress";
@ -182,15 +181,6 @@ class HaConfigBackupOverview extends LitElement {
>
</ha-backup-overview-progress>
`
: this.fetching
? html`
<ha-backup-summary-card
heading="Loading backups"
description="Your backup information is being retrieved."
status="loading"
>
</ha-backup-summary-card>
`
: this._needsOnboarding
? html`
<ha-backup-overview-onboarding
@ -204,6 +194,7 @@ class HaConfigBackupOverview extends LitElement {
.hass=${this.hass}
.backups=${this.backups}
.config=${this.config}
.fetching=${this.fetching}
>
</ha-backup-overview-summary>
`}

View File

@ -257,6 +257,12 @@ class HaConfigBackupSettings extends LitElement {
.alert {
--mdc-theme-primary: var(--error-color);
}
.card-header {
padding-bottom: 8px;
}
.card-content {
padding-bottom: 0;
}
`;
}