Filter backups by type (#23366)

* filter backups by type

* Add filter pane

* Make sure we show all when you click show all
This commit is contained in:
Bram Kragten 2024-12-20 20:52:11 +01:00 committed by GitHub
parent 33df805168
commit b3b0006ba3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 131 additions and 17 deletions

View File

@ -1,9 +1,9 @@
import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list";
import type { SelectedDetail } from "@material/mwc-list"; import type { List, SelectedDetail } from "@material/mwc-list";
import { mdiFilterVariantRemove } from "@mdi/js"; import { mdiFilterVariantRemove } from "@mdi/js";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } 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 { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { haStyleScrollbar } from "../resources/styles"; import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
@ -32,6 +32,8 @@ export class HaFilterStates extends LitElement {
@state() private _shouldRender = false; @state() private _shouldRender = false;
@query("mwc-list") private _list!: List;
protected render() { protected render() {
if (!this.states) { if (!this.states) {
return nothing; return nothing;
@ -84,12 +86,21 @@ export class HaFilterStates extends LitElement {
`; `;
} }
protected updated(changed) { protected willUpdate(changed) {
if (changed.has("expanded") && this.expanded) { if (changed.has("expanded") && this.expanded) {
setTimeout(() => { this._shouldRender = true;
}
}
protected updated(changed) {
if ((changed.has("expanded") || changed.has("states")) && this.expanded) {
setTimeout(async () => {
if (!this.expanded) return; if (!this.expanded) return;
this.renderRoot.querySelector("mwc-list")!.style.height = const list = this._list;
`${this.clientHeight - 49}px`; if (!list) {
return;
}
list.style.height = `${this.clientHeight - 49}px`;
}, 300); }, 300);
} }
} }

View File

@ -62,7 +62,10 @@ class HaBackupOverviewBackups extends LitElement {
<div class="card-header">My backups</div> <div class="card-header">My backups</div>
<div class="card-content"> <div class="card-content">
<ha-md-list> <ha-md-list>
<ha-md-list-item type="link" href="/config/backup/backups"> <ha-md-list-item
type="link"
href="/config/backup/backups?type=automatic"
>
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
<div slot="headline"> <div slot="headline">
${automaticStats.count} automatic backups ${automaticStats.count} automatic backups
@ -72,7 +75,10 @@ class HaBackupOverviewBackups extends LitElement {
</div> </div>
<ha-icon-next slot="end"></ha-icon-next> <ha-icon-next slot="end"></ha-icon-next>
</ha-md-list-item> </ha-md-list-item>
<ha-md-list-item type="link" href="/config/backup/backups"> <ha-md-list-item
type="link"
href="/config/backup/backups?type=manual"
>
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon> <ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
<div slot="headline">${manualStats.count} manual backups</div> <div slot="headline">${manualStats.count} manual backups</div>
<div slot="supporting-text"> <div slot="supporting-text">
@ -83,7 +89,10 @@ class HaBackupOverviewBackups extends LitElement {
</ha-md-list> </ha-md-list>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-button href="/config/backup/backups" @click=${this._showAll}> <ha-button
href="/config/backup/backups?type=all"
@click=${this._showAll}
>
Show all backups Show all backups
</ha-button> </ha-button>
</div> </div>

View File

@ -28,6 +28,7 @@ import type {
import "../../../components/ha-button"; import "../../../components/ha-button";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-filter-states";
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-icon-overflow-menu";
@ -46,6 +47,7 @@ import {
} from "../../../data/backup"; } from "../../../data/backup";
import type { ManagerStateEvent } from "../../../data/backup_manager"; import type { ManagerStateEvent } from "../../../data/backup_manager";
import type { CloudStatus } from "../../../data/cloud"; import type { CloudStatus } from "../../../data/cloud";
import type { DataTableFiltersValues } from "../../../data/data_table_filters";
import { extractApiErrorMessage } from "../../../data/hassio/common"; import { extractApiErrorMessage } from "../../../data/hassio/common";
import { import {
showAlertDialog, showAlertDialog,
@ -89,6 +91,14 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
@state() private _selected: string[] = []; @state() private _selected: string[] = [];
@storage({
storage: "sessionStorage",
key: "backups-table-filters",
state: true,
subscribe: false,
})
private _filters: DataTableFiltersValues = {};
@storage({ key: "backups-table-grouping", state: false, subscribe: false }) @storage({ key: "backups-table-grouping", state: false, subscribe: false })
private _activeGrouping?: string = "formatted_type"; private _activeGrouping?: string = "formatted_type";
@ -102,6 +112,27 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
public connectedCallback() {
super.connectedCallback();
window.addEventListener("location-changed", this._locationChanged);
window.addEventListener("popstate", this._popState);
this._setFiltersFromUrl();
}
disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("location-changed", this._locationChanged);
window.removeEventListener("popstate", this._popState);
}
private _locationChanged = () => {
this._setFiltersFromUrl();
};
private _popState = () => {
this._setFiltersFromUrl();
};
private _columns = memoizeOne( private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer<BackupRow> => ({ (localize: LocalizeFunc): DataTableColumnContainer<BackupRow> => ({
name: { name: {
@ -230,13 +261,28 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
return capitalizeFirstLetter(type); return capitalizeFirstLetter(type);
} }
private _data = memoizeOne((backups: BackupContent[]): BackupRow[] => private _data = memoizeOne(
backups.map((backup) => ({ (
...backup, backups: BackupContent[],
formatted_type: this._formatBackupType( filters: DataTableFiltersValues
backup.with_automatic_settings ? "automatic" : "manual" ): BackupRow[] => {
), const typeFilter = filters["ha-filter-states"] as string[] | undefined;
})) let filteredBackups = backups;
if (typeFilter?.length) {
filteredBackups = filteredBackups.filter(
(backup) =>
(backup.with_automatic_settings &&
typeFilter.includes("automatic")) ||
(!backup.with_automatic_settings && typeFilter.includes("manual"))
);
}
return filteredBackups.map((backup) => ({
...backup,
formatted_type: this._formatBackupType(
backup.with_automatic_settings ? "automatic" : "manual"
),
}));
}
); );
protected render(): TemplateResult { protected render(): TemplateResult {
@ -257,6 +303,15 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
back-path="/config/backup/overview" back-path="/config/backup/overview"
clickable clickable
id="backup_id" id="backup_id"
has-filters
.filters=${Object.values(this._filters).filter((filter) =>
Array.isArray(filter)
? filter.length
: filter &&
Object.values(filter).some((val) =>
Array.isArray(val) ? val.length : val
)
).length}
selectable selectable
.selected=${this._selected.length} .selected=${this._selected.length}
.initialGroupColumn=${this._activeGrouping} .initialGroupColumn=${this._activeGrouping}
@ -268,7 +323,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
.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)}
.data=${this._data(this.backups)} .data=${this._data(this.backups, this._filters)}
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")} .noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
.searchLabel=${this.hass.localize( .searchLabel=${this.hass.localize(
"ui.panel.config.backup.picker.search" "ui.panel.config.backup.picker.search"
@ -324,6 +379,16 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
</div> </div>
</div> ` </div> `
: nothing} : nothing}
<ha-filter-states
.hass=${this.hass}
label="Type"
.value=${this._filters["ha-filter-states"]}
.states=${this._states(this.hass.localize)}
@data-table-filter-changed=${this._filterChanged}
slot="filter-pane"
expanded
.narrow=${this.narrow}
></ha-filter-states>
${!this._needsOnboarding ${!this._needsOnboarding
? html` ? html`
<ha-fab <ha-fab
@ -341,6 +406,35 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
`; `;
} }
private _states = memoizeOne((_localize: LocalizeFunc) => [
{
value: "automatic",
label: "Automatic",
},
{
value: "manual",
label: "Manual",
},
]);
private _filterChanged(ev) {
const type = ev.target.localName;
this._filters = { ...this._filters, [type]: ev.detail.value };
}
private _setFiltersFromUrl() {
const searchParams = new URLSearchParams(window.location.search);
const type = searchParams.get("type");
if (!type) {
return;
}
this._filters = {
"ha-filter-states": type === "all" ? [] : [type],
};
}
private get _needsOnboarding() { private get _needsOnboarding() {
return !this.config?.create_backup.password; return !this.config?.create_backup.password;
} }