mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Add support for add-on update type for backups in the UI (#24044)
* Add support for add-on update type for backups in the UI * Add type to backup detail page * Use new model * Fix detail page * Fix type
This commit is contained in:
parent
3a12019b64
commit
11ae3a77e8
@ -1,6 +1,8 @@
|
||||
import { memoize } from "@fullcalendar/core/internal";
|
||||
import { setHours, setMinutes } from "date-fns";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import memoizeOne from "memoize-one";
|
||||
import checkValidDate from "../common/datetime/check_valid_date";
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDateTimeNumeric,
|
||||
@ -11,7 +13,6 @@ import type { HomeAssistant } from "../types";
|
||||
import { fileDownload } from "../util/file_download";
|
||||
import { domainToName } from "./integration";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
import checkValidDate from "../common/datetime/check_valid_date";
|
||||
|
||||
export const enum BackupScheduleRecurrence {
|
||||
NEVER = "never",
|
||||
@ -104,6 +105,9 @@ export interface BackupContent {
|
||||
name: string;
|
||||
agents: Record<string, BackupContentAgent>;
|
||||
failed_agent_ids?: string[];
|
||||
extra_metadata?: {
|
||||
"supervisor.addon_update"?: string;
|
||||
};
|
||||
with_automatic_settings: boolean;
|
||||
}
|
||||
|
||||
@ -319,6 +323,29 @@ export const computeBackupAgentName = (
|
||||
export const computeBackupSize = (backup: BackupContent) =>
|
||||
Math.max(...Object.values(backup.agents).map((agent) => agent.size));
|
||||
|
||||
export type BackupType = "automatic" | "manual" | "addon_update";
|
||||
|
||||
const BACKUP_TYPE_ORDER: BackupType[] = ["automatic", "manual", "addon_update"];
|
||||
|
||||
export const getBackupTypes = memoize((isHassio: boolean) =>
|
||||
isHassio
|
||||
? BACKUP_TYPE_ORDER
|
||||
: BACKUP_TYPE_ORDER.filter((type) => type !== "addon_update")
|
||||
);
|
||||
|
||||
export const computeBackupType = (
|
||||
backup: BackupContent,
|
||||
isHassio: boolean
|
||||
): BackupType => {
|
||||
if (backup.with_automatic_settings) {
|
||||
return "automatic";
|
||||
}
|
||||
if (isHassio && backup.extra_metadata?.["supervisor.addon_update"] != null) {
|
||||
return "addon_update";
|
||||
}
|
||||
return "manual";
|
||||
};
|
||||
|
||||
export const compareAgents = (a: string, b: string) => {
|
||||
const isLocalA = isLocalAgent(a);
|
||||
const isLocalB = isLocalAgent(b);
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { mdiCalendarSync, mdiGestureTap } from "@mdi/js";
|
||||
import { mdiCalendarSync, mdiGestureTap, mdiPuzzle } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import type { BackupContent, BackupType } from "../../../../../data/backup";
|
||||
import {
|
||||
computeBackupSize,
|
||||
type BackupContent,
|
||||
computeBackupType,
|
||||
getBackupTypes,
|
||||
} from "../../../../../data/backup";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
@ -21,6 +24,12 @@ interface BackupStats {
|
||||
size: number;
|
||||
}
|
||||
|
||||
const TYPE_ICONS: Record<BackupType, string> = {
|
||||
automatic: mdiCalendarSync,
|
||||
manual: mdiGestureTap,
|
||||
addon_update: mdiPuzzle,
|
||||
};
|
||||
|
||||
const computeBackupStats = (backups: BackupContent[]): BackupStats =>
|
||||
backups.reduce(
|
||||
(stats, backup) => {
|
||||
@ -37,23 +46,22 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||
|
||||
private _automaticStats = memoizeOne((backups: BackupContent[]) => {
|
||||
const automaticBackups = backups.filter(
|
||||
(backup) => backup.with_automatic_settings
|
||||
);
|
||||
return computeBackupStats(automaticBackups);
|
||||
});
|
||||
|
||||
private _manualStats = memoizeOne((backups: BackupContent[]) => {
|
||||
const manualBackups = backups.filter(
|
||||
(backup) => !backup.with_automatic_settings
|
||||
);
|
||||
return computeBackupStats(manualBackups);
|
||||
});
|
||||
private _stats = memoizeOne(
|
||||
(
|
||||
backups: BackupContent[],
|
||||
isHassio: boolean
|
||||
): [BackupType, BackupStats][] =>
|
||||
getBackupTypes(isHassio).map((type) => {
|
||||
const backupsOfType = backups.filter(
|
||||
(backup) => computeBackupType(backup, isHassio) === type
|
||||
);
|
||||
return [type, computeBackupStats(backupsOfType)] as const;
|
||||
})
|
||||
);
|
||||
|
||||
render() {
|
||||
const automaticStats = this._automaticStats(this.backups);
|
||||
const manualStats = this._manualStats(this.backups);
|
||||
const isHassio = isComponentLoaded(this.hass, "hassio");
|
||||
const stats = this._stats(this.backups, isHassio);
|
||||
|
||||
return html`
|
||||
<ha-card class="my-backups">
|
||||
@ -62,44 +70,32 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/backup/backups?type=automatic"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.automatic",
|
||||
{ count: automaticStats.count }
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.total_size",
|
||||
{ size: bytesToString(automaticStats.size, 1) }
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/backup/backups?type=manual"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.manual",
|
||||
{ count: manualStats.count }
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.total_size",
|
||||
{ size: bytesToString(manualStats.size, 1) }
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
${stats.map(
|
||||
([type, { count, size }]) => html`
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/backup/backups?type=${type}"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${TYPE_ICONS[type]}
|
||||
></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.overview.backups.${type}`,
|
||||
{ count }
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.total_size",
|
||||
{ size: bytesToString(size) }
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
|
@ -11,6 +11,7 @@ import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
@ -42,9 +43,11 @@ import {
|
||||
compareAgents,
|
||||
computeBackupAgentName,
|
||||
computeBackupSize,
|
||||
computeBackupType,
|
||||
deleteBackup,
|
||||
generateBackup,
|
||||
generateBackupWithAutomaticSettings,
|
||||
getBackupTypes,
|
||||
isLocalAgent,
|
||||
isNetworkMountAgent,
|
||||
} from "../../../data/backup";
|
||||
@ -74,10 +77,6 @@ interface BackupRow extends DataTableRowData, BackupContent {
|
||||
agent_ids: string[];
|
||||
}
|
||||
|
||||
type BackupType = "automatic" | "manual";
|
||||
|
||||
const TYPE_ORDER: BackupType[] = ["automatic", "manual"];
|
||||
|
||||
@customElement("ha-config-backup-backups")
|
||||
class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -277,9 +276,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
private _groupOrder = memoizeOne(
|
||||
(activeGrouping: string | undefined, localize: LocalizeFunc) =>
|
||||
(
|
||||
activeGrouping: string | undefined,
|
||||
localize: LocalizeFunc,
|
||||
isHassio: boolean
|
||||
) =>
|
||||
activeGrouping === "formatted_type"
|
||||
? TYPE_ORDER.map((type) =>
|
||||
? getBackupTypes(isHassio).map((type) =>
|
||||
localize(`ui.panel.config.backup.type.${type}`)
|
||||
)
|
||||
: undefined
|
||||
@ -303,20 +306,19 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
(
|
||||
backups: BackupContent[],
|
||||
filters: DataTableFiltersValues,
|
||||
localize: LocalizeFunc
|
||||
localize: LocalizeFunc,
|
||||
isHassio: boolean
|
||||
): BackupRow[] => {
|
||||
const typeFilter = filters["ha-filter-states"] as string[] | undefined;
|
||||
let filteredBackups = backups;
|
||||
if (typeFilter?.length) {
|
||||
filteredBackups = filteredBackups.filter(
|
||||
(backup) =>
|
||||
(backup.with_automatic_settings &&
|
||||
typeFilter.includes("automatic")) ||
|
||||
(!backup.with_automatic_settings && typeFilter.includes("manual"))
|
||||
);
|
||||
filteredBackups = filteredBackups.filter((backup) => {
|
||||
const type = computeBackupType(backup, isHassio);
|
||||
return typeFilter.includes(type);
|
||||
});
|
||||
}
|
||||
return filteredBackups.map((backup) => {
|
||||
const type = backup.with_automatic_settings ? "automatic" : "manual";
|
||||
const type = computeBackupType(backup, isHassio);
|
||||
const agentIds = Object.keys(backup.agents);
|
||||
return {
|
||||
...backup,
|
||||
@ -335,8 +337,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
protected render(): TemplateResult {
|
||||
const backupInProgress =
|
||||
"state" in this.manager && this.manager.state === "in_progress";
|
||||
|
||||
const data = this._data(this.backups, this._filters, this.hass.localize);
|
||||
const isHassio = isComponentLoaded(this.hass, "hassio");
|
||||
const data = this._data(
|
||||
this.backups,
|
||||
this._filters,
|
||||
this.hass.localize,
|
||||
isHassio
|
||||
);
|
||||
const maxDisplayedAgents = Math.min(
|
||||
this._maxAgents(data),
|
||||
this.narrow ? 3 : 5
|
||||
@ -371,7 +378,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
.initialCollapsedGroups=${this._activeCollapsed}
|
||||
.groupOrder=${this._groupOrder(
|
||||
this._activeGrouping,
|
||||
this.hass.localize
|
||||
this.hass.localize,
|
||||
isHassio
|
||||
)}
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
@ -435,7 +443,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize("ui.panel.config.backup.backup_type")}
|
||||
.value=${this._filters["ha-filter-states"]}
|
||||
.states=${this._states(this.hass.localize)}
|
||||
.states=${this._states(this.hass.localize, isHassio)}
|
||||
@data-table-filter-changed=${this._filterChanged}
|
||||
slot="filter-pane"
|
||||
expanded
|
||||
@ -460,8 +468,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _states = memoizeOne((localize: LocalizeFunc) =>
|
||||
TYPE_ORDER.map((type) => ({
|
||||
private _states = memoizeOne((localize: LocalizeFunc, isHassio: boolean) =>
|
||||
getBackupTypes(isHassio).map((type) => ({
|
||||
value: type,
|
||||
label: localize(`ui.panel.config.backup.type.${type}`),
|
||||
}))
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
compareAgents,
|
||||
computeBackupAgentName,
|
||||
computeBackupSize,
|
||||
computeBackupType,
|
||||
deleteBackup,
|
||||
fetchBackupDetails,
|
||||
isLocalAgent,
|
||||
@ -46,6 +47,7 @@ import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { downloadBackup } from "./helper/download_backup";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
|
||||
interface Agent extends BackupContentAgent {
|
||||
id: string;
|
||||
@ -110,6 +112,8 @@ class HaConfigBackupDetails extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const isHassio = isComponentLoaded(this.hass, "hassio");
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup/backups"
|
||||
@ -161,6 +165,18 @@ class HaConfigBackupDetails extends LitElement {
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list class="summary">
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.backup_type"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.type.${computeBackupType(this._backup, isHassio)}`
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
|
@ -2223,7 +2223,8 @@
|
||||
"backup_type": "Type",
|
||||
"type": {
|
||||
"manual": "Manual",
|
||||
"automatic": "Automatic"
|
||||
"automatic": "Automatic",
|
||||
"addon_update": "Add-on update"
|
||||
},
|
||||
"locations": "Locations",
|
||||
"create": {
|
||||
@ -2566,6 +2567,7 @@
|
||||
"title": "My backups",
|
||||
"automatic": "{count} automatic {count, plural,\n one {backup}\n other {backups}\n}",
|
||||
"manual": "{count} manual {count, plural,\n one {backup}\n other {backups}\n}",
|
||||
"addon_update": "{count} add-on update {count, plural,\n one {backup}\n other {backups}\n}",
|
||||
"total_size": "{size} in total",
|
||||
"show_all": "Show all backups"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user