mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Use failed add-ons and folders of backup (#25548)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
208e863327
commit
bb5f01ac81
@ -103,12 +103,20 @@ export interface BackupContentAgent {
|
||||
protected: boolean;
|
||||
}
|
||||
|
||||
export interface AddonInfo {
|
||||
name: string | null;
|
||||
slug: string;
|
||||
version: string | null;
|
||||
}
|
||||
|
||||
export interface BackupContent {
|
||||
backup_id: string;
|
||||
date: string;
|
||||
name: string;
|
||||
agents: Record<string, BackupContentAgent>;
|
||||
failed_agent_ids?: string[];
|
||||
failed_addons?: AddonInfo[];
|
||||
failed_folders?: string[];
|
||||
extra_metadata?: {
|
||||
"supervisor.addon_update"?: string;
|
||||
};
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||
import {
|
||||
computeBackupSize,
|
||||
computeBackupType,
|
||||
type BackupContentExtended,
|
||||
} from "../../../../data/backup";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { bytesToString } from "../../../../util/bytes-to-string";
|
||||
|
||||
@customElement("ha-backup-details-summary")
|
||||
@ -28,12 +30,35 @@ class HaBackupDetailsSummary extends LitElement {
|
||||
this.hass.config
|
||||
);
|
||||
|
||||
const errors: { title: string; items: string[] }[] = [];
|
||||
if (this.backup.failed_addons?.length) {
|
||||
errors.push({
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.error.failed_addons"
|
||||
),
|
||||
items: this.backup.failed_addons.map(
|
||||
(addon) => `${addon.name || addon.slug} (${addon.version})`
|
||||
),
|
||||
});
|
||||
}
|
||||
if (this.backup.failed_folders?.length) {
|
||||
errors.push({
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.error.failed_folders"
|
||||
),
|
||||
items: this.backup.failed_folders.map((folder) =>
|
||||
this._localizeFolder(folder)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize("ui.panel.config.backup.details.summary.title")}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
${errors.length ? this._renderErrorSummary(errors) : nothing}
|
||||
<ha-md-list class="summary">
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
@ -69,6 +94,45 @@ class HaBackupDetailsSummary extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderErrorSummary(errors: { title: string; items: string[] }[]) {
|
||||
return html`
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.error.title"
|
||||
)}
|
||||
>
|
||||
${errors.map(
|
||||
({ title, items }) => html`
|
||||
<br />
|
||||
<b>${title}:</b>
|
||||
<ul>
|
||||
${items.map((item) => html`<li>${item}</li>`)}
|
||||
</ul>
|
||||
`
|
||||
)}
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
private _localizeFolder(folder: string): string {
|
||||
switch (folder) {
|
||||
case "media":
|
||||
return this.hass.localize(`ui.panel.config.backup.data_picker.media`);
|
||||
case "share":
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.data_picker.share_folder`
|
||||
);
|
||||
case "ssl":
|
||||
return this.hass.localize(`ui.panel.config.backup.data_picker.ssl`);
|
||||
case "addons/local":
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.data_picker.local_addons`
|
||||
);
|
||||
}
|
||||
return capitalizeFirstLetter(folder);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
max-width: 690px;
|
||||
|
@ -4,13 +4,18 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
formatDate,
|
||||
formatDateWeekday,
|
||||
} from "../../../../../common/datetime/format_date";
|
||||
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
||||
import type { LocalizeKeys } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
||||
import {
|
||||
BackupScheduleRecurrence,
|
||||
@ -18,12 +23,8 @@ import {
|
||||
} from "../../../../../data/backup";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-backup-summary-card";
|
||||
import {
|
||||
formatDate,
|
||||
formatDateWeekday,
|
||||
} from "../../../../../common/datetime/format_date";
|
||||
import { showAlertDialog } from "../../../../lovelace/custom-card-helpers";
|
||||
import "../ha-backup-summary-card";
|
||||
|
||||
const OVERDUE_MARGIN_HOURS = 3;
|
||||
|
||||
@ -55,29 +56,57 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
);
|
||||
});
|
||||
|
||||
private _renderSummaryCard(
|
||||
heading: string,
|
||||
status: "error" | "info" | "warning" | "loading" | "success",
|
||||
headline: string | null,
|
||||
description?: string | null,
|
||||
lastCompletedDate?: Date
|
||||
) {
|
||||
return html`
|
||||
<ha-backup-summary-card .heading=${heading} .status=${status}>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline" class=${headline === null ? "skeleton" : ""}
|
||||
>${headline}</span
|
||||
>
|
||||
</ha-md-list-item>
|
||||
${description || description === null
|
||||
? html`<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span
|
||||
slot="headline"
|
||||
class=${description === null ? "skeleton" : ""}
|
||||
>${description}</span
|
||||
>
|
||||
|
||||
${lastCompletedDate
|
||||
? html` <ha-icon-button
|
||||
slot="end"
|
||||
@click=${this._createAdditionalBackupDescription(
|
||||
lastCompletedDate
|
||||
)}
|
||||
.path=${mdiInformation}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</ha-md-list-item>`
|
||||
: nothing}
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const now = new Date();
|
||||
|
||||
if (this.fetching) {
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.loading"
|
||||
)}
|
||||
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>
|
||||
`;
|
||||
return this._renderSummaryCard(
|
||||
this.hass.localize("ui.panel.config.backup.overview.summary.loading"),
|
||||
"loading",
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
const lastBackup = this._lastBackup(this.backups);
|
||||
@ -137,18 +166,12 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
if (lastAttemptDate > lastCompletedDate) {
|
||||
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
return this._renderSummaryCard(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_heading"
|
||||
)}
|
||||
status="error"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
),
|
||||
"error",
|
||||
this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_description",
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
@ -158,18 +181,9 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
true
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
${lastUploadedBackup || nextBackupDescription
|
||||
? html`
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiCalendar}
|
||||
></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${lastUploadedBackup
|
||||
),
|
||||
lastUploadedBackup || nextBackupDescription
|
||||
? lastUploadedBackup
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||
{
|
||||
@ -179,67 +193,60 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
now,
|
||||
true
|
||||
),
|
||||
count: Object.keys(lastUploadedBackup.agents)
|
||||
.length,
|
||||
count: Object.keys(lastUploadedBackup.agents).length,
|
||||
}
|
||||
)
|
||||
: nextBackupDescription}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
: nextBackupDescription
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
// If no backups yet, show warning
|
||||
if (!lastBackup) {
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
return this._renderSummaryCard(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.no_backup_heading"
|
||||
)}
|
||||
status="warning"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
),
|
||||
"warning",
|
||||
this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.no_backup_description"
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
${this._renderNextBackupDescription(
|
||||
),
|
||||
nextBackupDescription,
|
||||
lastCompletedDate,
|
||||
showAdditionalBackupDescription
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
showAdditionalBackupDescription ? lastCompletedDate : undefined
|
||||
);
|
||||
}
|
||||
|
||||
const lastBackupDate = new Date(lastBackup.date);
|
||||
|
||||
// If last backup
|
||||
if (lastBackup.failed_agent_ids?.length) {
|
||||
// if parts of the last backup failed
|
||||
if (
|
||||
lastBackup.failed_agent_ids?.length ||
|
||||
lastBackup.failed_addons?.length ||
|
||||
lastBackup.failed_folders?.length
|
||||
) {
|
||||
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
const failedTypes: string[] = [];
|
||||
|
||||
if (lastBackup.failed_agent_ids?.length) {
|
||||
failedTypes.push("locations");
|
||||
}
|
||||
if (lastBackup.failed_addons?.length) {
|
||||
failedTypes.push("addons");
|
||||
}
|
||||
if (lastBackup.failed_folders?.length) {
|
||||
failedTypes.push("folders");
|
||||
}
|
||||
|
||||
const type = failedTypes.join("_");
|
||||
|
||||
return this._renderSummaryCard(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_heading"
|
||||
)}
|
||||
status="error"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_locations_description",
|
||||
),
|
||||
"error",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.backup.overview.summary.last_backup_failed_${type}_description` as LocalizeKeys,
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
lastAttemptDate,
|
||||
@ -248,15 +255,8 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
true
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
|
||||
${lastUploadedBackup || nextBackupDescription
|
||||
? html` <ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${lastUploadedBackup
|
||||
),
|
||||
lastUploadedBackup
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||
{
|
||||
@ -266,17 +266,12 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
now,
|
||||
true
|
||||
),
|
||||
count: Object.keys(lastUploadedBackup.agents)
|
||||
.length,
|
||||
count: Object.keys(lastUploadedBackup.agents).length,
|
||||
}
|
||||
)
|
||||
: nextBackupDescription}
|
||||
</span>
|
||||
</ha-md-list-item>`
|
||||
: nothing}
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
: nextBackupDescription,
|
||||
showAdditionalBackupDescription ? lastCompletedDate : undefined
|
||||
);
|
||||
}
|
||||
|
||||
const lastSuccessfulBackupDescription = this.hass.localize(
|
||||
@ -303,37 +298,20 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
this.config.schedule.recurrence === BackupScheduleRecurrence.DAILY) ||
|
||||
numberOfDays >= 7;
|
||||
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
return this._renderSummaryCard(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.backup.overview.summary.${isOverdue ? "backup_too_old_heading" : "backup_success_heading"}`,
|
||||
{ count: numberOfDays }
|
||||
)}
|
||||
.status=${isOverdue ? "warning" : "success"}
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">${lastSuccessfulBackupDescription}</span>
|
||||
</ha-md-list-item>
|
||||
${this._renderNextBackupDescription(
|
||||
),
|
||||
isOverdue ? "warning" : "success",
|
||||
lastSuccessfulBackupDescription,
|
||||
nextBackupDescription,
|
||||
lastCompletedDate,
|
||||
showAdditionalBackupDescription
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
showAdditionalBackupDescription ? lastCompletedDate : undefined
|
||||
);
|
||||
}
|
||||
|
||||
private _renderNextBackupDescription(
|
||||
nextBackupDescription: string,
|
||||
lastCompletedDate: Date,
|
||||
showTip = false
|
||||
) {
|
||||
// handle edge case that there is an additional backup scheduled
|
||||
const openAdditionalBackupDescriptionDialog = showTip
|
||||
? () => {
|
||||
private _createAdditionalBackupDescription =
|
||||
(lastCompletedDate: Date) => () => {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.additional_backup_description",
|
||||
@ -346,24 +324,7 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return nextBackupDescription
|
||||
? html`<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span slot="headline">${nextBackupDescription}</span>
|
||||
|
||||
${showTip
|
||||
? html` <ha-icon-button
|
||||
slot="end"
|
||||
@click=${openAdditionalBackupDescriptionDialog}
|
||||
.path=${mdiInformation}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</ha-md-list-item>`
|
||||
: nothing;
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
|
@ -2654,6 +2654,12 @@
|
||||
"last_backup_failed_heading": "Last automatic backup failed",
|
||||
"last_backup_failed_description": "The last automatic backup triggered {relative_time} wasn't successful.",
|
||||
"last_backup_failed_locations_description": "The last automatic backup created {relative_time} wasn't stored in all locations.",
|
||||
"last_backup_failed_addons_description": "The last automatic backup created {relative_time} was not able to backup all add-ons.",
|
||||
"last_backup_failed_folders_description": "The last automatic backup created {relative_time} was not able to backup all folders.",
|
||||
"last_backup_failed_addons_folders_description": "The last automatic backup created {relative_time} wasn't able to backup all add-ons and folders.",
|
||||
"last_backup_failed_locations_addons_description": "The last automatic backup created {relative_time} wasn't able to backup all add-ons and wasn't stored in all locations.",
|
||||
"last_backup_failed_locations_folders_description": "The last automatic backup created {relative_time} wasn't able to backup all folders and wasn't stored in all locations.",
|
||||
"last_backup_failed_locations_addons_folders_description": "The last automatic backup created {relative_time} wasn't able to backup all add-ons and folders and wasn't stored in all locations.",
|
||||
"last_successful_backup_description": "Last successful automatic backup {relative_time} and stored in {count} {count, plural,\n one {location}\n other {locations}\n}.",
|
||||
"no_backup_heading": "No automatic backup available",
|
||||
"no_backup_description": "You have no automatic backups yet.",
|
||||
@ -2769,7 +2775,13 @@
|
||||
"summary": {
|
||||
"title": "Backup",
|
||||
"size": "Size",
|
||||
"created": "Created"
|
||||
"created": "Created",
|
||||
"error": {
|
||||
"title": "This backup was not created successfully. Some data is missing.",
|
||||
"failed_locations": "Failed locations",
|
||||
"failed_addons": "Failed add-ons",
|
||||
"failed_folders": "Failed folders"
|
||||
}
|
||||
},
|
||||
"restore": {
|
||||
"title": "Select what to restore",
|
||||
|
Loading…
x
Reference in New Issue
Block a user