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; public hasAction = false;
@property() @property()
private status: SummaryStatus = "info"; public status: SummaryStatus = "info";
render() { render() {
return html` return html`

View File

@ -1,28 +1,36 @@
import { mdiPlus } from "@mdi/js"; import { mdiDelete, mdiPlus } from "@mdi/js";
import type { PropertyValues, TemplateResult } from "lit"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import type { LocalizeFunc } from "../../../common/translations/localize"; import type { LocalizeFunc } from "../../../common/translations/localize";
import type { import type {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-button"; import "../../../components/ha-button";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { import {
fetchBackupInfo, fetchBackupInfo,
generateBackup, generateBackup,
removeBackup,
type BackupContent, type BackupContent,
} from "../../../data/backup"; } from "../../../data/backup";
import { extractApiErrorMessage } from "../../../data/hassio/common";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types"; import type { HomeAssistant, Route } from "../../../types";
import { brandsUrl } from "../../../util/brands-url"; import { brandsUrl } from "../../../util/brands-url";
import { import {
@ -43,6 +51,11 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
@state() private _backups: BackupContent[] = []; @state() private _backups: BackupContent[] = [];
@state() private _selected: string[] = [];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer<BackupContent> => ({ (localize: LocalizeFunc): DataTableColumnContainer<BackupContent> => ({
name: { 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 { protected render(): TemplateResult {
return html` return html`
<hass-tabs-subpage-data-table <hass-tabs-subpage-data-table
@ -108,6 +150,9 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
back-path="/config/system" back-path="/config/system"
clickable clickable
id="backup_id" id="backup_id"
selectable
.selected=${this._selected.length}
@selection-changed=${this._handleSelectionChanged}
.route=${this.route} .route=${this.route}
@row-click=${this._showBackupDetails} @row-click=${this._showBackupDetails}
.columns=${this._columns(this.hass.localize)} .columns=${this._columns(this.hass.localize)}
@ -139,6 +184,41 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
</ha-button> </ha-button>
</ha-backup-summary-card> </ha-backup-summary-card>
</div> </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 <ha-fab
slot="fab" slot="fab"
?disabled=${this._backingUp} ?disabled=${this._backingUp}
@ -197,6 +277,49 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
navigate(`/config/backup/details/${id}`); 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() { private _configureAutomaticBackup() {
navigate("/config/backup/automatic-config"); navigate("/config/backup/automatic-config");
} }
@ -205,28 +328,71 @@ class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
navigate("/config/backup/locations"); navigate("/config/backup/locations");
} }
static styles = css` static get styles(): CSSResultGroup {
.header { return [
padding: 16px 16px 0 16px; haStyle,
display: flex; css`
flex-direction: row; .header {
gap: 16px; padding: 16px 16px 0 16px;
background-color: var(--primary-background-color); display: flex;
} flex-direction: row;
@media (max-width: 1000px) { gap: 16px;
.header { background-color: var(--primary-background-color);
flex-direction: column; }
} @media (max-width: 1000px) {
} .header {
.header > * { flex-direction: column;
flex: 1; }
min-width: 0; }
} .header > * {
flex: 1;
min-width: 0;
}
ha-fab[disabled] { ha-fab[disabled] {
--mdc-theme-secondary: var(--disabled-text-color) !important; --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 { declare global {