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;
|
row-gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@ -145,10 +146,6 @@ class HaBackupSummaryCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 550px) {
|
@media all and (max-width: 550px) {
|
||||||
.summary {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
.action {
|
.action {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
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;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
.card-content {
|
.card-content {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 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-md-list-item";
|
||||||
import "../../../../../components/ha-svg-icon";
|
import "../../../../../components/ha-svg-icon";
|
||||||
import type { BackupConfig } from "../../../../../data/backup";
|
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 { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
|
||||||
@ -88,6 +92,14 @@ class HaBackupBackupsSummary extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (offsiteLocations.length) {
|
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`;
|
return `Upload to ${offsiteLocations.length} off-site locations`;
|
||||||
}
|
}
|
||||||
if (hasLocal) {
|
if (hasLocal) {
|
||||||
@ -184,9 +196,13 @@ class HaBackupBackupsSummary extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
.card-content {
|
.card-content {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
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 type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -17,6 +17,8 @@ import { haStyle } from "../../../../../resources/styles";
|
|||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-backup-summary-card";
|
import "../ha-backup-summary-card";
|
||||||
|
|
||||||
|
const OVERDUE_MARGIN_HOURS = 3;
|
||||||
|
|
||||||
@customElement("ha-backup-overview-summary")
|
@customElement("ha-backup-overview-summary")
|
||||||
class HaBackupOverviewBackups extends LitElement {
|
class HaBackupOverviewBackups extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -25,9 +27,14 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public config!: BackupConfig;
|
@property({ attribute: false }) public config!: BackupConfig;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public fetching = false;
|
||||||
|
|
||||||
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
||||||
const sortedBackups = backups
|
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());
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
|
|
||||||
return sortedBackups[0] as BackupContent | undefined;
|
return sortedBackups[0] as BackupContent | undefined;
|
||||||
@ -60,6 +67,23 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
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);
|
const lastBackup = this._lastBackup(this.backups);
|
||||||
|
|
||||||
if (!lastBackup) {
|
if (!lastBackup) {
|
||||||
@ -75,10 +99,9 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
const lastBackupDate = new Date(lastBackup.date);
|
const lastBackupDate = new Date(lastBackup.date);
|
||||||
|
|
||||||
const numberOfDays = differenceInDays(new Date(), lastBackupDate);
|
|
||||||
const now = new Date();
|
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(
|
const nextBackupDescription = this._nextBackupDescription(
|
||||||
this.config.schedule.state
|
this.config.schedule.state
|
||||||
);
|
);
|
||||||
@ -94,51 +117,62 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
heading=${`Last automatic backup failed`}
|
heading=${`Last automatic backup failed`}
|
||||||
status="error"
|
status="error"
|
||||||
>
|
>
|
||||||
<ul class="list">
|
<ha-md-list>
|
||||||
<li class="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>${lastAttemptDescription}</span>
|
<span slot="headline">${lastAttemptDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
<li class="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>${lastBackupDescription}</span>
|
<span slot="headline">${lastBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
</ul>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</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`
|
return html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-summary-card
|
||||||
heading=${`No backup for ${numberOfDays} days`}
|
heading=${`No backup for ${numberOfDays} days`}
|
||||||
status="warning"
|
status="warning"
|
||||||
>
|
>
|
||||||
<ul class="list">
|
<ha-md-list>
|
||||||
<li class="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>${lastBackupDescription}</span>
|
<span slot="headline">${lastBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
<li class="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>${nextBackupDescription}</span>
|
<span slot="headline">${nextBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
</ul>
|
</ha-md-list>
|
||||||
</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">
|
||||||
<ul class="list">
|
<ha-md-list>
|
||||||
<li class="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>${lastBackupDescription}</span>
|
<span slot="headline">${lastBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
<li class="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>${nextBackupDescription}</span>
|
<span slot="headline">${nextBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
</ul>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -156,25 +190,6 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
p {
|
p {
|
||||||
margin: 0;
|
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 {
|
ha-svg-icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
@ -183,6 +198,42 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
border-top: none;
|
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 { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import "./components/ha-backup-summary-card";
|
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-backups";
|
||||||
import "./components/overview/ha-backup-overview-onboarding";
|
import "./components/overview/ha-backup-overview-onboarding";
|
||||||
import "./components/overview/ha-backup-overview-progress";
|
import "./components/overview/ha-backup-overview-progress";
|
||||||
@ -182,31 +181,23 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-backup-overview-progress>
|
</ha-backup-overview-progress>
|
||||||
`
|
`
|
||||||
: this.fetching
|
: this._needsOnboarding
|
||||||
? html`
|
? html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-overview-onboarding
|
||||||
heading="Loading backups"
|
.hass=${this.hass}
|
||||||
description="Your backup information is being retrieved."
|
@button-click=${this._handleOnboardingButtonClick}
|
||||||
status="loading"
|
|
||||||
>
|
>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-overview-onboarding>
|
||||||
`
|
`
|
||||||
: this._needsOnboarding
|
: html`
|
||||||
? html`
|
<ha-backup-overview-summary
|
||||||
<ha-backup-overview-onboarding
|
.hass=${this.hass}
|
||||||
.hass=${this.hass}
|
.backups=${this.backups}
|
||||||
@button-click=${this._handleOnboardingButtonClick}
|
.config=${this.config}
|
||||||
>
|
.fetching=${this.fetching}
|
||||||
</ha-backup-overview-onboarding>
|
>
|
||||||
`
|
</ha-backup-overview-summary>
|
||||||
: html`
|
`}
|
||||||
<ha-backup-overview-summary
|
|
||||||
.hass=${this.hass}
|
|
||||||
.backups=${this.backups}
|
|
||||||
.config=${this.config}
|
|
||||||
>
|
|
||||||
</ha-backup-overview-summary>
|
|
||||||
`}
|
|
||||||
|
|
||||||
<ha-backup-overview-backups
|
<ha-backup-overview-backups
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -257,6 +257,12 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
.alert {
|
.alert {
|
||||||
--mdc-theme-primary: var(--error-color);
|
--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