diff --git a/hassio/src/snapshots/hassio-snapshots.ts b/hassio/src/snapshots/hassio-snapshots.ts
index a13f37c9d2..56c527b667 100644
--- a/hassio/src/snapshots/hassio-snapshots.ts
+++ b/hassio/src/snapshots/hassio-snapshots.ts
@@ -1,15 +1,17 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
-import { mdiDotsVertical, mdiPlus } from "@mdi/js";
+import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
import {
+ css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} 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 { atLeastVersion } from "../../../src/common/config/version";
import relativeTime from "../../../src/common/datetime/relative_time";
@@ -17,18 +19,25 @@ import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
RowClickedEvent,
+ SelectionChangedEvent,
} from "../../../src/components/data-table/ha-data-table";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
+import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioSnapshots,
friendlyFolderName,
HassioSnapshot,
reloadHassioSnapshots,
+ removeSnapshot,
} from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
-import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
+import {
+ showAlertDialog,
+ showConfirmationDialog,
+} from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-tabs-subpage-data-table";
+import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
@@ -49,10 +58,15 @@ export class HassioSnapshots extends LitElement {
@property({ type: Boolean }) public isWide!: boolean;
- private _firstUpdatedCalled = false;
+ @state() private _selectedSnapshots: string[] = [];
@state() private _snapshots?: HassioSnapshot[] = [];
+ @query("hass-tabs-subpage-data-table", true)
+ private _dataTable!: HaTabsSubpageDataTable;
+
+ private _firstUpdatedCalled = false;
+
public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
@@ -153,7 +167,9 @@ export class HassioSnapshots extends LitElement {
.data=${this._snapshotData(this._snapshots || [])}
id="slug"
@row-click=${this._handleRowClicked}
+ @selection-changed=${this._handleSelectionChanged}
clickable
+ selectable
hasFab
main-page
supervisor
@@ -176,6 +192,45 @@ export class HassioSnapshots extends LitElement {
: ""}
+ ${this._selectedSnapshots.length
+ ? html`
+
+ ${this.supervisor.localize("snapshot.selected", {
+ number: this._selectedSnapshots.length,
+ })}
+
+
+
`
+ : ""}
+
+ ): void {
+ this._selectedSnapshots = ev.detail.value;
+ }
+
private _showUploadSnapshotDialog() {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) =>
@@ -216,6 +277,35 @@ export class HassioSnapshots extends LitElement {
this._snapshots = await fetchHassioSnapshots(this.hass);
}
+ private async _deleteSelected() {
+ const confirm = await showConfirmationDialog(this, {
+ title: this.supervisor.localize("snapshot.delete_snapshot_title"),
+ text: this.supervisor.localize("snapshot.delete_snapshot_text", {
+ number: this._selectedSnapshots.length,
+ }),
+ confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
+ });
+
+ if (!confirm) {
+ return;
+ }
+
+ try {
+ await Promise.all(
+ this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
+ );
+ } catch (err) {
+ showAlertDialog(this, {
+ title: this.supervisor.localize("snapshot.failed_to_delete"),
+ text: extractApiErrorMessage(err),
+ });
+ return;
+ }
+ await reloadHassioSnapshots(this.hass);
+ this._snapshots = await fetchHassioSnapshots(this.hass);
+ this._dataTable.clearSelection();
+ }
+
private _handleRowClicked(ev: HASSDomEvent) {
const slug = ev.detail.id;
showHassioSnapshotDialog(this, {
@@ -244,7 +334,45 @@ export class HassioSnapshots extends LitElement {
}
static get styles(): CSSResultGroup {
- return [haStyle, hassioStyle];
+ return [
+ haStyle,
+ hassioStyle,
+ css`
+ .table-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 58px;
+ border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
+ }
+ .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;
+ 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;
+ }
+ .header-btns > mwc-button,
+ .header-btns > mwc-icon-button {
+ margin: 8px;
+ }
+ `,
+ ];
}
}
diff --git a/src/data/hassio/snapshot.ts b/src/data/hassio/snapshot.ts
index ba34776228..b2f1c36c25 100644
--- a/src/data/hassio/snapshot.ts
+++ b/src/data/hassio/snapshot.ts
@@ -130,6 +130,21 @@ export const createHassioFullSnapshot = async (
);
};
+export const removeSnapshot = async (hass: HomeAssistant, slug: string) => {
+ if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
+ await hass.callWS({
+ type: "supervisor/api",
+ endpoint: `/snapshots/${slug}/remove`,
+ method: "post",
+ });
+ return;
+ }
+ await hass.callApi>(
+ "POST",
+ `hassio/snapshots/${slug}/remove`
+ );
+};
+
export const createHassioPartialSnapshot = async (
hass: HomeAssistant,
data: HassioPartialSnapshotCreateParams
diff --git a/src/translations/en.json b/src/translations/en.json
index 6a700c664a..584e25f33e 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -3896,6 +3896,12 @@
"available_snapshots": "Available Snapshots",
"no_snapshots": "You don't have any snapshots yet.",
"create_blocked_not_running": "Creating a snapshot is not possible right now because the system is in {state} state.",
+ "delete_selected": "Delete selected snapshots",
+ "delete_snapshot_title": "Delete snapshot",
+ "delete_snapshot_text": "Do you want to delete {number} {number, plural,\n one {snapshot}\n other {snapshots}\n}?",
+ "delete_snapshot_confirm": "delete",
+ "selected": "{number} selected",
+ "failed_to_delete": "Failed to delete",
"could_not_create": "Could not create snapshot",
"upload_snapshot": "Upload snapshot",
"create_snapshot": "Create snapshot",