From be6ecefb9e422b97e0e8a361ed23a1c1e5fe407c Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 19 Nov 2024 11:53:15 +0100 Subject: [PATCH] Add delete backup action to datatable (#22867) --- .../components/ha-backup-summary-card.ts | 2 +- .../backup/ha-config-backup-dashboard.ts | 216 ++++++++++++++++-- 2 files changed, 192 insertions(+), 26 deletions(-) diff --git a/src/panels/config/backup/components/ha-backup-summary-card.ts b/src/panels/config/backup/components/ha-backup-summary-card.ts index ba5822e4b4..8ac74ed930 100644 --- a/src/panels/config/backup/components/ha-backup-summary-card.ts +++ b/src/panels/config/backup/components/ha-backup-summary-card.ts @@ -34,7 +34,7 @@ class HaBackupSummaryCard extends LitElement { public hasAction = false; @property() - private status: SummaryStatus = "info"; + public status: SummaryStatus = "info"; render() { return html` diff --git a/src/panels/config/backup/ha-config-backup-dashboard.ts b/src/panels/config/backup/ha-config-backup-dashboard.ts index 1df2b2bea8..0bf26fa3a9 100644 --- a/src/panels/config/backup/ha-config-backup-dashboard.ts +++ b/src/panels/config/backup/ha-config-backup-dashboard.ts @@ -1,28 +1,36 @@ -import { mdiPlus } from "@mdi/js"; -import type { PropertyValues, TemplateResult } from "lit"; -import { css, html, LitElement } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { mdiDelete, mdiPlus } from "@mdi/js"; +import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { relativeTime } from "../../../common/datetime/relative_time"; +import type { HASSDomEvent } from "../../../common/dom/fire_event"; import { navigate } from "../../../common/navigate"; import type { LocalizeFunc } from "../../../common/translations/localize"; import type { DataTableColumnContainer, RowClickedEvent, + SelectionChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-button"; import "../../../components/ha-card"; import "../../../components/ha-fab"; import "../../../components/ha-icon"; import "../../../components/ha-icon-next"; +import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-svg-icon"; import { fetchBackupInfo, generateBackup, + removeBackup, type BackupContent, } from "../../../data/backup"; +import { extractApiErrorMessage } from "../../../data/hassio/common"; import "../../../layouts/hass-tabs-subpage-data-table"; +import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; +import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; import { @@ -43,6 +51,11 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) { @state() private _backups: BackupContent[] = []; + @state() private _selected: string[] = []; + + @query("hass-tabs-subpage-data-table", true) + private _dataTable!: HaTabsSubpageDataTable; + private _columns = memoizeOne( (localize: LocalizeFunc): DataTableColumnContainer => ({ name: { @@ -90,9 +103,38 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) { `; })}`, }, + actions: { + title: "", + label: localize("ui.panel.config.generic.headers.actions"), + showNarrow: true, + moveable: false, + hideable: false, + type: "overflow-menu", + template: (backup) => html` + this._deleteBackup(backup), + warning: true, + }, + ]} + > + + `, + }, }) ); + private _handleSelectionChanged( + ev: HASSDomEvent + ): void { + this._selected = ev.detail.value; + } + protected render(): TemplateResult { return html` + + ${this._selected.length + ? html`
+

+ ${this._selected.length} backups selected +

+
+ ${!this.narrow + ? html` + + Delete selected + + ` + : html` + + + Delete selected + + `} +
+
` + : nothing} + { + const confirm = await showConfirmationDialog(this, { + title: "Delete backup", + text: "This backup will be permanently deleted.", + confirmText: this.hass.localize("ui.common.delete"), + destructive: true, + }); + + if (!confirm) { + return; + } + + await removeBackup(this.hass, backup.backup_id); + this._fetchBackupInfo(); + } + + private async _deleteSelected() { + const confirm = await showConfirmationDialog(this, { + title: "Delete selected backups", + text: "These backups will be permanently deleted.", + confirmText: this.hass.localize("ui.common.delete"), + destructive: true, + }); + + if (!confirm) { + return; + } + + try { + await Promise.all( + this._selected.map((slug) => removeBackup(this.hass, slug)) + ); + } catch (err: any) { + showAlertDialog(this, { + title: "Failed to delete backups", + text: extractApiErrorMessage(err), + }); + return; + } + await this._fetchBackupInfo(); + this._dataTable.clearSelection(); + } + private _configureAutomaticBackup() { navigate("/config/backup/automatic-config"); } @@ -205,28 +328,71 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) { navigate("/config/backup/locations"); } - static styles = css` - .header { - padding: 16px 16px 0 16px; - display: flex; - flex-direction: row; - gap: 16px; - background-color: var(--primary-background-color); - } - @media (max-width: 1000px) { - .header { - flex-direction: column; - } - } - .header > * { - flex: 1; - min-width: 0; - } + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + .header { + padding: 16px 16px 0 16px; + display: flex; + flex-direction: row; + gap: 16px; + background-color: var(--primary-background-color); + } + @media (max-width: 1000px) { + .header { + flex-direction: column; + } + } + .header > * { + flex: 1; + min-width: 0; + } - ha-fab[disabled] { - --mdc-theme-secondary: var(--disabled-text-color) !important; - } - `; + ha-fab[disabled] { + --mdc-theme-secondary: var(--disabled-text-color) !important; + } + + .table-header { + display: flex; + justify-content: space-between; + align-items: center; + height: var(--header-height); + box-sizing: border-box; + } + .header-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + color: var(--secondary-text-color); + position: relative; + top: -4px; + } + .selected-txt { + font-weight: bold; + padding-left: 16px; + padding-inline-start: 16px; + padding-inline-end: initial; + color: var(--primary-text-color); + } + .table-header .selected-txt { + margin-top: 20px; + } + .header-toolbar .selected-txt { + font-size: 16px; + } + .header-toolbar .header-btns { + margin-right: -12px; + margin-inline-end: -12px; + margin-inline-start: initial; + } + .header-btns > ha-button, + .header-btns > ha-icon-button { + margin: 8px; + } + `, + ]; + } } declare global {