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