Add delete backup action to datatable (#22867)

This commit is contained in:
Paul Bottein 2024-11-19 11:53:15 +01:00 committed by GitHub
parent 99bde50c01
commit be6ecefb9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 192 additions and 26 deletions

View File

@ -34,7 +34,7 @@ class HaBackupSummaryCard extends LitElement {
public hasAction = false;
@property()
private status: SummaryStatus = "info";
public status: SummaryStatus = "info";
render() {
return html`

View File

@ -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<BackupContent> => ({
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`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
label: this.hass.localize("ui.common.delete"),
path: mdiDelete,
action: () => this._deleteBackup(backup),
warning: true,
},
]}
>
</ha-icon-overflow-menu>
`,
},
})
);
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selected = ev.detail.value;
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage-data-table
@ -108,6 +150,9 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
back-path="/config/system"
clickable
id="backup_id"
selectable
.selected=${this._selected.length}
@selection-changed=${this._handleSelectionChanged}
.route=${this.route}
@row-click=${this._showBackupDetails}
.columns=${this._columns(this.hass.localize)}
@ -139,6 +184,41 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
</ha-button>
</ha-backup-summary-card>
</div>
${this._selected.length
? html`<div
class=${classMap({
"header-toolbar": this.narrow,
"table-header": !this.narrow,
})}
slot="header"
>
<p class="selected-txt">
${this._selected.length} backups selected
</p>
<div class="header-btns">
${!this.narrow
? html`
<ha-button @click=${this._deleteSelected} class="warning">
Delete selected
</ha-button>
`
: html`
<ha-icon-button
.label=${"Delete selected"}
.path=${mdiDelete}
id="delete-btn"
class="warning"
@click=${this._deleteSelected}
></ha-icon-button>
<simple-tooltip animation-delay="0" for="delete-btn">
Delete selected
</simple-tooltip>
`}
</div>
</div> `
: nothing}
<ha-fab
slot="fab"
?disabled=${this._backingUp}
@ -197,6 +277,49 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
navigate(`/config/backup/details/${id}`);
}
private async _deleteBackup(backup: BackupContent): Promise<void> {
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 {