mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
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:
parent
76cb9ce807
commit
5fb384ad31
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -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,31 +181,23 @@ class HaConfigBackupOverview extends LitElement {
|
||||
>
|
||||
</ha-backup-overview-progress>
|
||||
`
|
||||
: this.fetching
|
||||
: this._needsOnboarding
|
||||
? html`
|
||||
<ha-backup-summary-card
|
||||
heading="Loading backups"
|
||||
description="Your backup information is being retrieved."
|
||||
status="loading"
|
||||
<ha-backup-overview-onboarding
|
||||
.hass=${this.hass}
|
||||
@button-click=${this._handleOnboardingButtonClick}
|
||||
>
|
||||
</ha-backup-summary-card>
|
||||
</ha-backup-overview-onboarding>
|
||||
`
|
||||
: this._needsOnboarding
|
||||
? html`
|
||||
<ha-backup-overview-onboarding
|
||||
.hass=${this.hass}
|
||||
@button-click=${this._handleOnboardingButtonClick}
|
||||
>
|
||||
</ha-backup-overview-onboarding>
|
||||
`
|
||||
: html`
|
||||
<ha-backup-overview-summary
|
||||
.hass=${this.hass}
|
||||
.backups=${this.backups}
|
||||
.config=${this.config}
|
||||
>
|
||||
</ha-backup-overview-summary>
|
||||
`}
|
||||
: html`
|
||||
<ha-backup-overview-summary
|
||||
.hass=${this.hass}
|
||||
.backups=${this.backups}
|
||||
.config=${this.config}
|
||||
.fetching=${this.fetching}
|
||||
>
|
||||
</ha-backup-overview-summary>
|
||||
`}
|
||||
|
||||
<ha-backup-overview-backups
|
||||
.hass=${this.hass}
|
||||
|
@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user