From 6dc7e852ae72a5fda65d055444a4a2a591630a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 10 May 2021 18:17:16 +0200 Subject: [PATCH] Use hass-tabs-subpage-data-table for supervisor snapshots (#9103) * Use hass-tabs-subpage-data-table for supervisor snapshots * comments * type * cleanup * change translations * Update hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts Co-authored-by: Bram Kragten * reset * fix after rebase * internalProperty -> state Co-authored-by: Bram Kragten --- .../snapshot/dialog-hassio-create-snapshot.ts | 368 ++++++++++ .../snapshot/dialog-hassio-snapshot.ts | 21 +- .../show-dialog-hassio-create-snapshot.ts | 18 + hassio/src/snapshots/hassio-snapshots.ts | 483 ++++---------- src/components/data-table/ha-data-table.ts | 631 +++++++++--------- src/layouts/hass-tabs-subpage-data-table.ts | 4 + src/translations/en.json | 2 + 7 files changed, 840 insertions(+), 687 deletions(-) create mode 100755 hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts create mode 100644 hassio/src/dialogs/snapshot/show-dialog-hassio-create-snapshot.ts diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts new file mode 100755 index 0000000000..b97354f9a0 --- /dev/null +++ b/hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts @@ -0,0 +1,368 @@ +import "@material/mwc-button"; +import "@polymer/paper-input/paper-input"; +import type { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { + css, + CSSResult, + customElement, + html, + state, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { formatDate } from "../../../../src/common/datetime/format_date"; +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import { compare } from "../../../../src/common/string/compare"; +import "../../../../src/components/buttons/ha-progress-button"; +import "../../../../src/components/ha-checkbox"; +import type { HaCheckbox } from "../../../../src/components/ha-checkbox"; +import { createCloseHeading } from "../../../../src/components/ha-dialog"; +import "../../../../src/components/ha-formfield"; +import "../../../../src/components/ha-radio"; +import type { HaRadio } from "../../../../src/components/ha-radio"; +import "../../../../src/components/ha-settings-row"; +import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; +import { + createHassioFullSnapshot, + createHassioPartialSnapshot, + HassioFullSnapshotCreateParams, + HassioPartialSnapshotCreateParams, + HassioSnapshot, +} from "../../../../src/data/hassio/snapshot"; +import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; +import { PolymerChangedEvent } from "../../../../src/polymer-types"; +import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; +import { HomeAssistant } from "../../../../src/types"; +import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot"; + +interface CheckboxItem { + slug: string; + checked: boolean; + name?: string; + version?: string; +} + +const folderList = () => [ + { + slug: "homeassistant", + checked: true, + }, + { slug: "ssl", checked: true }, + { slug: "share", checked: true }, + { slug: "media", checked: true }, + { slug: "addons/local", checked: true }, +]; + +@customElement("dialog-hassio-create-snapshot") +class HassioCreateSnapshotDialog extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _snapshotName = ""; + + @state() private _snapshotPassword = ""; + + @state() private _snapshotHasPassword = false; + + @state() private _snapshotType: HassioSnapshot["type"] = "full"; + + @state() private _dialogParams?: HassioCreateSnapshotDialogParams; + + @state() private _addonList: CheckboxItem[] = []; + + @state() private _folderList: CheckboxItem[] = folderList(); + + @state() private _error = ""; + + public showDialog(params: HassioCreateSnapshotDialogParams) { + this._dialogParams = params; + this._addonList = this._dialogParams.supervisor.supervisor.addons + .map((addon) => ({ + slug: addon.slug, + name: addon.name, + version: addon.version, + checked: true, + })) + .sort((a, b) => compare(a.name, b.name)); + this._snapshotType = "full"; + this._error = ""; + this._folderList = folderList(); + this._snapshotHasPassword = false; + this._snapshotPassword = ""; + this._snapshotName = ""; + } + + public closeDialog() { + this._dialogParams = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._dialogParams) { + return html``; + } + return html` + + + +
+
+ ${this._dialogParams.supervisor.localize("snapshot.type")}: +
+ + + + + + + + +
+ + ${ + this._snapshotType === "full" + ? undefined + : html` + ${this._dialogParams.supervisor.localize("snapshot.folders")}: +
+ ${this._folderList.map( + (folder, idx) => html` +
+ + + + ${this._dialogParams!.supervisor.localize( + `snapshot.folder.${folder.slug}` + )} + +
+ ` + )} +
+ + ${this._dialogParams.supervisor.localize("snapshot.addons")}: +
+ ${this._addonList.map( + (addon, idx) => html` +
+ + + + ${addon.name} + (${addon.version}) + + +
+ ` + )} +
+ ` + } + ${this._dialogParams.supervisor.localize("snapshot.security")}: +
+
+ + + + ${this._dialogParams.supervisor.localize( + "snapshot.password_protection" + )} + + +
+
+ + ${ + this._snapshotHasPassword + ? html` + + + ` + : undefined + } + ${ + this._error !== "" + ? html`

${this._error}

` + : undefined + } + + ${this._dialogParams.supervisor.localize("common.close")} + + + ${this._dialogParams.supervisor.localize("snapshot.create")} + +
+ `; + } + + private _handleTextValueChanged(ev: PolymerChangedEvent) { + const input = ev.currentTarget as PaperInputElement; + this[`_${input.name}`] = ev.detail.value; + } + + private _handleCheckboxValueChanged(ev: CustomEvent) { + const input = ev.currentTarget as HaCheckbox; + this._snapshotHasPassword = input.checked; + } + + private _handleRadioValueChanged(ev: CustomEvent) { + const input = ev.currentTarget as HaRadio; + this[`_${input.name}`] = input.value; + } + + private _folderChecked(ev) { + const { idx, checked } = ev.currentTarget!; + this._folderList = this._folderList.map((folder, curIdx) => + curIdx === idx ? { ...folder, checked } : folder + ); + } + + private _addonChecked(ev) { + const { idx, checked } = ev.currentTarget!; + this._addonList = this._addonList.map((addon, curIdx) => + curIdx === idx ? { ...addon, checked } : addon + ); + } + + private async _createSnapshot(ev: CustomEvent): Promise { + if (this._dialogParams!.supervisor.info.state !== "running") { + showAlertDialog(this, { + title: this._dialogParams!.supervisor.localize( + "snapshot.could_not_create" + ), + text: this._dialogParams!.supervisor.localize( + "snapshot.create_blocked_not_running", + "state", + this._dialogParams!.supervisor.info.state + ), + }); + return; + } + const button = ev.currentTarget as any; + button.progress = true; + + this._error = ""; + if (this._snapshotHasPassword && !this._snapshotPassword.length) { + this._error = this._dialogParams!.supervisor.localize( + "snapshot.enter_password" + ); + button.progress = false; + return; + } + const name = this._snapshotName || formatDate(new Date(), this.hass.locale); + + try { + if (this._snapshotType === "full") { + const data: HassioFullSnapshotCreateParams = { name }; + if (this._snapshotHasPassword) { + data.password = this._snapshotPassword; + } + await createHassioFullSnapshot(this.hass, data); + } else { + const data: HassioPartialSnapshotCreateParams = { + name, + folders: this._folderList + .filter((folder) => folder.checked) + .map((folder) => folder.slug), + addons: this._addonList + .filter((addon) => addon.checked) + .map((addon) => addon.slug), + }; + if (this._snapshotHasPassword) { + data.password = this._snapshotPassword; + } + await createHassioPartialSnapshot(this.hass, data); + } + + this._dialogParams!.onCreate(); + this.closeDialog(); + } catch (err) { + this._error = extractApiErrorMessage(err); + } + button.progress = false; + } + + static get styles(): CSSResult[] { + return [ + haStyle, + haStyleDialog, + css` + .error { + color: var(--error-color); + } + paper-input[type="password"] { + display: block; + margin: 4px 0 4px 16px; + } + span.version { + color: var(--secondary-text-color); + } + .checkbox-section { + display: grid; + } + .checkbox-line { + display: inline-flex; + align-items: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-hassio-create-snapshot": HassioCreateSnapshotDialog; + } +} diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts index c4e13a02d8..ebe2e9f774 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -13,6 +13,7 @@ import { property, TemplateResult, } from "lit-element"; +import { formatDateTime } from "../../../../src/common/datetime/format_date_time"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-svg-icon"; @@ -132,7 +133,7 @@ class HassioSnapshotDialog extends LitElement { ? "Full snapshot" : "Partial snapshot"} (${this._computeSize})
- ${this._formatDatetime(this._snapshot.date)} + ${formatDateTime(new Date(this._snapshot.date), this.hass.locale)} ${this._snapshot.homeassistant ? html`
Home Assistant:
@@ -142,7 +143,8 @@ class HassioSnapshotDialog extends LitElement { this._restoreHass = (ev.target as PaperCheckboxElement).checked!; }}" > - Home Assistant ${this._snapshot.homeassistant} + Home Assistant + (${this._snapshot.homeassistant}) ` : ""} ${this._folders.length @@ -181,6 +183,7 @@ class HassioSnapshotDialog extends LitElement { )}" > ${item.name} + (${item.version}) ` )} @@ -268,6 +271,9 @@ class HassioSnapshotDialog extends LitElement { .no-margin-top { margin-top: 0; } + span.version { + color: var(--secondary-text-color); + } ha-header-bar { --mdc-theme-on-primary: var(--primary-text-color); --mdc-theme-primary: var(--mdc-theme-surface); @@ -499,17 +505,6 @@ class HassioSnapshotDialog extends LitElement { return Math.ceil(this._snapshot!.size * 10) / 10 + " MB"; } - private _formatDatetime(datetime) { - return new Date(datetime).toLocaleDateString(navigator.language, { - weekday: "long", - year: "numeric", - month: "short", - day: "numeric", - hour: "numeric", - minute: "2-digit", - }); - } - private _closeDialog() { this._dialogParams = undefined; this._snapshot = undefined; diff --git a/hassio/src/dialogs/snapshot/show-dialog-hassio-create-snapshot.ts b/hassio/src/dialogs/snapshot/show-dialog-hassio-create-snapshot.ts new file mode 100644 index 0000000000..353caf31a2 --- /dev/null +++ b/hassio/src/dialogs/snapshot/show-dialog-hassio-create-snapshot.ts @@ -0,0 +1,18 @@ +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import { Supervisor } from "../../../../src/data/supervisor/supervisor"; + +export interface HassioCreateSnapshotDialogParams { + supervisor: Supervisor; + onCreate: () => void; +} + +export const showHassioCreateSnapshotDialog = ( + element: HTMLElement, + dialogParams: HassioCreateSnapshotDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-hassio-create-snapshot", + dialogImport: () => import("./dialog-hassio-create-snapshot"), + dialogParams, + }); +}; diff --git a/hassio/src/snapshots/hassio-snapshots.ts b/hassio/src/snapshots/hassio-snapshots.ts index c01310191f..6ed974dfca 100644 --- a/hassio/src/snapshots/hassio-snapshots.ts +++ b/hassio/src/snapshots/hassio-snapshots.ts @@ -1,118 +1,129 @@ import "@material/mwc-button"; -import "@material/mwc-icon-button"; -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; +import { mdiDotsVertical, mdiPlus } from "@mdi/js"; import { - mdiDotsVertical, - mdiPackageVariant, - mdiPackageVariantClosed, -} from "@mdi/js"; -import "@polymer/paper-checkbox/paper-checkbox"; -import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import "@polymer/paper-radio-button/paper-radio-button"; -import "@polymer/paper-radio-group/paper-radio-group"; -import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group"; -import { - css, CSSResultGroup, customElement, html, - state, LitElement, property, PropertyValues, + state, TemplateResult, } from "lit-element"; +import memoizeOne from "memoize-one"; import { atLeastVersion } from "../../../src/common/config/version"; -import "../../../src/components/buttons/ha-progress-button"; -import "../../../src/components/ha-button-menu"; -import "../../../src/components/ha-card"; -import "../../../src/components/ha-svg-icon"; -import { extractApiErrorMessage } from "../../../src/data/hassio/common"; +import relativeTime from "../../../src/common/datetime/relative_time"; +import { HASSDomEvent } from "../../../src/common/dom/fire_event"; +import { + DataTableColumnContainer, + RowClickedEvent, +} from "../../../src/components/data-table/ha-data-table"; +import "../../../src/components/ha-button-menu"; +import "../../../src/components/ha-fab"; import { - createHassioFullSnapshot, - createHassioPartialSnapshot, fetchHassioSnapshots, - HassioFullSnapshotCreateParams, - HassioPartialSnapshotCreateParams, HassioSnapshot, reloadHassioSnapshots, } from "../../../src/data/hassio/snapshot"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; -import "../../../src/layouts/hass-tabs-subpage"; -import { PolymerChangedEvent } from "../../../src/polymer-types"; +import "../../../src/layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; -import "../components/hassio-card-content"; -import "../components/hassio-upload-snapshot"; +import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot"; import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload"; import { supervisorTabs } from "../hassio-tabs"; import { hassioStyle } from "../resources/hassio-style"; -interface CheckboxItem { - slug: string; - checked: boolean; - name?: string; -} - @customElement("hassio-snapshots") -class HassioSnapshots extends LitElement { +export class HassioSnapshots extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; - - @property({ attribute: false }) public route!: Route; - @property({ attribute: false }) public supervisor!: Supervisor; - @state() private _snapshotName = ""; + @property({ type: Object }) public route!: Route; - @state() private _snapshotPassword = ""; + @property({ type: Boolean }) public narrow!: boolean; - @state() private _snapshotHasPassword = false; + @property({ type: Boolean }) public isWide!: boolean; - @state() private _snapshotType: HassioSnapshot["type"] = "full"; + private _firstUpdatedCalled = false; @state() private _snapshots?: HassioSnapshot[] = []; - @state() private _addonList: CheckboxItem[] = []; - - @state() private _folderList: CheckboxItem[] = [ - { - slug: "homeassistant", - checked: true, - }, - { slug: "ssl", checked: true }, - { slug: "share", checked: true }, - { slug: "media", checked: true }, - { slug: "addons/local", checked: true }, - ]; - - @state() private _error = ""; + public connectedCallback(): void { + super.connectedCallback(); + if (this.hass && this._firstUpdatedCalled) { + this.refreshData(); + } + } public async refreshData() { await reloadHassioSnapshots(this.hass); - await this._updateSnapshots(); + await this.fetchSnapshots(); } + protected firstUpdated(changedProperties: PropertyValues): void { + super.firstUpdated(changedProperties); + if (this.hass && this.isConnected) { + this.refreshData(); + } + this._firstUpdatedCalled = true; + } + + private _columns = memoizeOne( + (narrow: boolean): DataTableColumnContainer => ({ + name: { + title: this.supervisor?.localize("snapshot.name") || "", + sortable: true, + filterable: true, + grows: true, + template: (entry: string, snapshot: any) => entry || snapshot.slug, + }, + date: { + title: this.supervisor?.localize("snapshot.created") || "", + width: "15%", + direction: "desc", + hidden: narrow, + filterable: true, + sortable: true, + template: (entry: string) => + relativeTime(new Date(entry), this.hass.localize), + }, + type: { + title: this.supervisor?.localize("snapshot.type") || "", + width: "15%", + hidden: narrow, + filterable: true, + sortable: true, + template: (entry: string) => (entry === "partial" ? "Partial" : "Full"), + }, + }) + ); + protected render(): TemplateResult { + if (!this.supervisor) { + return html``; + } return html` - - - ${this.supervisor.localize("panel.snapshots")} - - ${this.supervisor.localize("common.reload")} + ${this.supervisor?.localize("common.reload")} ${atLeastVersion(this.hass.config.version, 0, 116) ? html` - ${this.supervisor.localize("snapshot.upload_snapshot")} + ${this.supervisor?.localize("snapshot.upload_snapshot")} ` : ""} -
-

${this.supervisor.localize("snapshot.create_snapshot")}

-

- ${this.supervisor.localize("snapshot.description")} -

-
- -
- - ${this.supervisor.localize("snapshot.type")}: - - - ${this.supervisor.localize("snapshot.full_snapshot")} - - - ${this.supervisor.localize("snapshot.partial_snapshot")} - - - ${this._snapshotType === "full" - ? undefined - : html` - ${this.supervisor.localize("snapshot.folders")}: - ${this._folderList.map( - (folder, idx) => html` - - ${this.supervisor.localize( - `snapshot.folder.${folder.slug}` - )} - - ` - )} - ${this.supervisor.localize("snapshot.addons")}: - ${this._addonList.map( - (addon, idx) => html` - - ${addon.name} - - ` - )} - `} - ${this.supervisor.localize("snapshot.security")}: - - ${this.supervisor.localize("snapshot.password_protection")} - - ${this._snapshotHasPassword - ? html` - - ` - : undefined} - ${this._error !== "" - ? html`

${this._error}

` - : undefined} -
-
- - ${this.supervisor.localize("snapshot.create")} - -
-
-
- -

${this.supervisor.localize("snapshot.available_snapshots")}

-
- ${this._snapshots === undefined - ? undefined - : this._snapshots.length === 0 - ? html` - -
- ${this.supervisor.localize("snapshot.no_snapshots")} -
-
- ` - : this._snapshots.map( - (snapshot) => html` - -
- -
-
- ` - )} -
-
-
+ + + + `; } - protected firstUpdated(changedProps: PropertyValues) { - super.firstUpdated(changedProps); - this.refreshData(); - } - - protected updated(changedProps: PropertyValues) { - if (changedProps.has("supervisor")) { - this._addonList = this.supervisor.supervisor.addons - .map((addon) => ({ - slug: addon.slug, - name: addon.name, - checked: true, - })) - .sort((a, b) => (a.name < b.name ? -1 : 1)); - } - } - private _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: @@ -299,157 +165,52 @@ class HassioSnapshots extends LitElement { } } - private _handleTextValueChanged(ev: PolymerChangedEvent) { - const input = ev.currentTarget as PaperInputElement; - this[`_${input.name}`] = ev.detail.value; - } - - private _handleCheckboxValueChanged(ev) { - const input = ev.currentTarget as PaperCheckboxElement; - this[`_${input.name}`] = input.checked; - } - - private _handleRadioValueChanged(ev: PolymerChangedEvent) { - const input = ev.currentTarget as PaperRadioGroupElement; - this[`_${input.getAttribute("name")}`] = ev.detail.value; - } - - private _folderChecked(ev) { - const { idx, checked } = ev.currentTarget!; - this._folderList = this._folderList.map((folder, curIdx) => - curIdx === idx ? { ...folder, checked } : folder - ); - } - - private _addonChecked(ev) { - const { idx, checked } = ev.currentTarget!; - this._addonList = this._addonList.map((addon, curIdx) => - curIdx === idx ? { ...addon, checked } : addon - ); - } - - private async _updateSnapshots() { - try { - this._snapshots = await fetchHassioSnapshots(this.hass); - this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1)); - } catch (err) { - this._error = extractApiErrorMessage(err); - } - } - - private async _createSnapshot(ev: CustomEvent): Promise { - if (this.supervisor.info.state !== "running") { - await showAlertDialog(this, { - title: this.supervisor.localize("snapshot.could_not_create"), - text: this.supervisor.localize( - "snapshot.create_blocked_not_running", - "state", - this.supervisor.info.state - ), - }); - } - const button = ev.currentTarget as any; - button.progress = true; - - this._error = ""; - if (this._snapshotHasPassword && !this._snapshotPassword.length) { - this._error = this.supervisor.localize("snapshot.enter_password"); - button.progress = false; - return; - } - await this.updateComplete; - - const name = - this._snapshotName || - new Date().toLocaleDateString(navigator.language, { - weekday: "long", - year: "numeric", - month: "short", - day: "numeric", - }); - - try { - if (this._snapshotType === "full") { - const data: HassioFullSnapshotCreateParams = { name }; - if (this._snapshotHasPassword) { - data.password = this._snapshotPassword; - } - await createHassioFullSnapshot(this.hass, data); - } else { - const addons = this._addonList - .filter((addon) => addon.checked) - .map((addon) => addon.slug); - const folders = this._folderList - .filter((folder) => folder.checked) - .map((folder) => folder.slug); - - const data: HassioPartialSnapshotCreateParams = { - name, - folders, - addons, - }; - if (this._snapshotHasPassword) { - data.password = this._snapshotPassword; - } - await createHassioPartialSnapshot(this.hass, data); - } - this._updateSnapshots(); - } catch (err) { - this._error = extractApiErrorMessage(err); - } - button.progress = false; - } - - private _computeDetails(snapshot: HassioSnapshot) { - const type = - snapshot.type === "full" - ? this.supervisor.localize("snapshot.full_snapshot") - : this.supervisor.localize("snapshot.partial_snapshot"); - return snapshot.protected ? `${type}, password protected` : type; - } - - private _snapshotClicked(ev) { - showHassioSnapshotDialog(this, { - slug: ev.currentTarget!.snapshot.slug, - supervisor: this.supervisor, - onDelete: () => this._updateSnapshots(), - }); - } - private _showUploadSnapshotDialog() { showSnapshotUploadDialog(this, { showSnapshot: (slug: string) => showHassioSnapshotDialog(this, { slug, supervisor: this.supervisor, - onDelete: () => this._updateSnapshots(), + onDelete: () => this.fetchSnapshots(), }), reloadSnapshot: () => this.refreshData(), }); } + private async fetchSnapshots() { + await reloadHassioSnapshots(this.hass); + this._snapshots = await fetchHassioSnapshots(this.hass); + } + + private _handleRowClicked(ev: HASSDomEvent) { + const slug = ev.detail.id; + showHassioSnapshotDialog(this, { + slug, + supervisor: this.supervisor, + onDelete: () => this.fetchSnapshots(), + }); + } + + private _createSnapshot() { + if (this.supervisor!.info.state !== "running") { + showAlertDialog(this, { + title: this.supervisor!.localize("snapshot.could_not_create"), + text: this.supervisor!.localize( + "snapshot.create_blocked_not_running", + "state", + this.supervisor!.info.state + ), + }); + return; + } + showHassioCreateSnapshotDialog(this, { + supervisor: this.supervisor!, + onCreate: () => this.fetchSnapshots(), + }); + } + static get styles(): CSSResultGroup { - return [ - haStyle, - hassioStyle, - css` - paper-radio-group { - display: block; - } - paper-radio-button { - padding: 0 0 2px 2px; - } - paper-radio-button, - paper-checkbox, - paper-input[type="password"] { - display: block; - margin: 4px 0 4px 48px; - } - .pointer { - cursor: pointer; - } - `, - ]; + return [haStyle, hassioStyle]; } } diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 639849eb7a..6a495afa9f 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -22,6 +22,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import "../../common/search/search-input"; import { debounce } from "../../common/util/debounce"; import { nextRender } from "../../common/util/render-status"; +import { haStyleScrollbar } from "../../resources/styles"; import "../ha-checkbox"; import type { HaCheckbox } from "../ha-checkbox"; import "../ha-icon"; @@ -327,7 +328,7 @@ export class HaDataTable extends LitElement { ` : html`
${scroll({ @@ -574,354 +575,358 @@ export class HaDataTable extends LitElement { } static get styles(): CSSResultGroup { - return css` - /* default mdc styles, colors changed, without checkbox styles */ - :host { - height: 100%; - } - .mdc-data-table__content { - font-family: Roboto, sans-serif; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 400; - letter-spacing: 0.0178571429em; - text-decoration: inherit; - text-transform: inherit; - } + return [ + haStyleScrollbar, + css` + /* default mdc styles, colors changed, without checkbox styles */ + :host { + height: 100%; + } + .mdc-data-table__content { + font-family: Roboto, sans-serif; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + letter-spacing: 0.0178571429em; + text-decoration: inherit; + text-transform: inherit; + } - .mdc-data-table { - background-color: var(--data-table-background-color); - border-radius: 4px; - border-width: 1px; - border-style: solid; - border-color: var(--divider-color); - display: inline-flex; - flex-direction: column; - box-sizing: border-box; - overflow: hidden; - } + .mdc-data-table { + background-color: var(--data-table-background-color); + border-radius: 4px; + border-width: 1px; + border-style: solid; + border-color: var(--divider-color); + display: inline-flex; + flex-direction: column; + box-sizing: border-box; + overflow: hidden; + } - .mdc-data-table__row--selected { - background-color: rgba(var(--rgb-primary-color), 0.04); - } + .mdc-data-table__row--selected { + background-color: rgba(var(--rgb-primary-color), 0.04); + } - .mdc-data-table__row { - display: flex; - width: 100%; - height: 52px; - } + .mdc-data-table__row { + display: flex; + width: 100%; + height: 52px; + } - .mdc-data-table__row ~ .mdc-data-table__row { - border-top: 1px solid var(--divider-color); - } + .mdc-data-table__row ~ .mdc-data-table__row { + border-top: 1px solid var(--divider-color); + } - .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { - background-color: rgba(var(--rgb-primary-text-color), 0.04); - } + .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { + background-color: rgba(var(--rgb-primary-text-color), 0.04); + } - .mdc-data-table__header-cell { - color: var(--primary-text-color); - } + .mdc-data-table__header-cell { + color: var(--primary-text-color); + } - .mdc-data-table__cell { - color: var(--primary-text-color); - } + .mdc-data-table__cell { + color: var(--primary-text-color); + } - .mdc-data-table__header-row { - height: 56px; - display: flex; - width: 100%; - border-bottom: 1px solid var(--divider-color); - overflow-x: auto; - } + .mdc-data-table__header-row { + height: 56px; + display: flex; + width: 100%; + border-bottom: 1px solid var(--divider-color); + overflow-x: auto; + } - .mdc-data-table__header-row::-webkit-scrollbar { - display: none; - } + .mdc-data-table__header-row::-webkit-scrollbar { + display: none; + } - .mdc-data-table__cell, - .mdc-data-table__header-cell { - padding-right: 16px; - padding-left: 16px; - align-self: center; - overflow: hidden; - text-overflow: ellipsis; - flex-shrink: 0; - box-sizing: border-box; - } + .mdc-data-table__cell, + .mdc-data-table__header-cell { + padding-right: 16px; + padding-left: 16px; + align-self: center; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 0; + box-sizing: border-box; + } - .mdc-data-table__cell.mdc-data-table__cell--icon { - overflow: initial; - } + .mdc-data-table__cell.mdc-data-table__cell--icon { + overflow: initial; + } - .mdc-data-table__header-cell--checkbox, - .mdc-data-table__cell--checkbox { - /* @noflip */ - padding-left: 16px; - /* @noflip */ - padding-right: 0; - width: 56px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--checkbox, - :host([dir="rtl"]) .mdc-data-table__cell--checkbox { - /* @noflip */ - padding-left: 0; - /* @noflip */ - padding-right: 16px; - } + .mdc-data-table__header-cell--checkbox, + .mdc-data-table__cell--checkbox { + /* @noflip */ + padding-left: 16px; + /* @noflip */ + padding-right: 0; + width: 56px; + } + :host([dir="rtl"]) .mdc-data-table__header-cell--checkbox, + :host([dir="rtl"]) .mdc-data-table__cell--checkbox { + /* @noflip */ + padding-left: 0; + /* @noflip */ + padding-right: 16px; + } - .mdc-data-table__table { - height: 100%; - width: 100%; - border: 0; - white-space: nowrap; - } + .mdc-data-table__table { + height: 100%; + width: 100%; + border: 0; + white-space: nowrap; + } - .mdc-data-table__cell { - font-family: Roboto, sans-serif; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 400; - letter-spacing: 0.0178571429em; - text-decoration: inherit; - text-transform: inherit; - } + .mdc-data-table__cell { + font-family: Roboto, sans-serif; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + letter-spacing: 0.0178571429em; + text-decoration: inherit; + text-transform: inherit; + } - .mdc-data-table__cell a { - color: inherit; - text-decoration: none; - } + .mdc-data-table__cell a { + color: inherit; + text-decoration: none; + } - .mdc-data-table__cell--numeric { - text-align: right; - } - :host([dir="rtl"]) .mdc-data-table__cell--numeric { - /* @noflip */ - text-align: left; - } + .mdc-data-table__cell--numeric { + text-align: right; + } + :host([dir="rtl"]) .mdc-data-table__cell--numeric { + /* @noflip */ + text-align: left; + } - .mdc-data-table__cell--icon { - color: var(--secondary-text-color); - text-align: center; - } + .mdc-data-table__cell--icon { + color: var(--secondary-text-color); + text-align: center; + } - .mdc-data-table__header-cell--icon, - .mdc-data-table__cell--icon { - width: 54px; - } + .mdc-data-table__header-cell--icon, + .mdc-data-table__cell--icon { + width: 54px; + } - .mdc-data-table__header-cell.mdc-data-table__header-cell--icon { - text-align: center; - } + .mdc-data-table__header-cell.mdc-data-table__header-cell--icon { + text-align: center; + } - .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover, - .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(.not-sorted) { - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover, - :host([dir="rtl"]) .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(.not-sorted) { - text-align: right; - } + text-align: left; + } + :host([dir="rtl"]) + .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover, + :host([dir="rtl"]) + .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not(.not-sorted) { + text-align: right; + } - .mdc-data-table__cell--icon:first-child ha-icon { - margin-left: 8px; - } - :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon { - margin-left: auto; - margin-right: 8px; - } + .mdc-data-table__cell--icon:first-child ha-icon { + margin-left: 8px; + } + :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon { + margin-left: auto; + margin-right: 8px; + } - .mdc-data-table__cell--icon:first-child state-badge { - margin-right: -8px; - } - :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child state-badge { - margin-right: auto; - margin-left: -8px; - } + .mdc-data-table__cell--icon:first-child state-badge { + margin-right: -8px; + } + :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child state-badge { + margin-right: auto; + margin-left: -8px; + } - .mdc-data-table__header-cell--icon-button, - .mdc-data-table__cell--icon-button { - width: 56px; - padding: 8px; - } + .mdc-data-table__header-cell--icon-button, + .mdc-data-table__cell--icon-button { + width: 56px; + padding: 8px; + } - .mdc-data-table__cell--icon-button { - color: var(--secondary-text-color); - text-overflow: clip; - } + .mdc-data-table__cell--icon-button { + color: var(--secondary-text-color); + text-overflow: clip; + } - .mdc-data-table__header-cell--icon-button:first-child, - .mdc-data-table__cell--icon-button:first-child { - width: 64px; - padding-left: 16px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:first-child, - :host([dir="rtl"]) .mdc-data-table__cell--icon-button:first-child { - padding-left: auto; - padding-right: 16px; - } + .mdc-data-table__header-cell--icon-button:first-child, + .mdc-data-table__cell--icon-button:first-child { + width: 64px; + padding-left: 16px; + } + :host([dir="rtl"]) + .mdc-data-table__header-cell--icon-button:first-child, + :host([dir="rtl"]) .mdc-data-table__cell--icon-button:first-child { + padding-left: auto; + padding-right: 16px; + } - .mdc-data-table__header-cell--icon-button:last-child, - .mdc-data-table__cell--icon-button:last-child { - width: 64px; - padding-right: 16px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child, - :host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child { - padding-right: auto; - padding-left: 16px; - } + .mdc-data-table__header-cell--icon-button:last-child, + .mdc-data-table__cell--icon-button:last-child { + width: 64px; + padding-right: 16px; + } + :host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child, + :host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child { + padding-right: auto; + padding-left: 16px; + } - .mdc-data-table__cell--icon-button a { - color: var(--secondary-text-color); - } + .mdc-data-table__cell--icon-button a { + color: var(--secondary-text-color); + } - .mdc-data-table__header-cell { - font-family: Roboto, sans-serif; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - font-size: 0.875rem; - line-height: 1.375rem; - font-weight: 500; - letter-spacing: 0.0071428571em; - text-decoration: inherit; - text-transform: inherit; - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell { - /* @noflip */ - text-align: right; - } + .mdc-data-table__header-cell { + font-family: Roboto, sans-serif; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + font-size: 0.875rem; + line-height: 1.375rem; + font-weight: 500; + letter-spacing: 0.0071428571em; + text-decoration: inherit; + text-transform: inherit; + text-align: left; + } + :host([dir="rtl"]) .mdc-data-table__header-cell { + /* @noflip */ + text-align: right; + } - .mdc-data-table__header-cell--numeric { - text-align: right; - } - .mdc-data-table__header-cell--numeric.sortable:hover, - .mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) { - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--numeric { - /* @noflip */ - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--numeric.sortable:hover, - :host([dir="rtl"]) + .mdc-data-table__header-cell--numeric { + text-align: right; + } + .mdc-data-table__header-cell--numeric.sortable:hover, .mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) { - text-align: right; - } + text-align: left; + } + :host([dir="rtl"]) .mdc-data-table__header-cell--numeric { + /* @noflip */ + text-align: left; + } + :host([dir="rtl"]) .mdc-data-table__header-cell--numeric.sortable:hover, + :host([dir="rtl"]) + .mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) { + text-align: right; + } - /* custom from here */ + /* custom from here */ - :host { - display: block; - } + :host { + display: block; + } - .mdc-data-table { - display: block; - border-width: var(--data-table-border-width, 1px); - height: 100%; - } - .mdc-data-table__header-cell { - overflow: hidden; - position: relative; - } - .mdc-data-table__header-cell span { - position: relative; - left: 0px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell span { - left: auto; - right: 0px; - } + .mdc-data-table { + display: block; + border-width: var(--data-table-border-width, 1px); + height: 100%; + } + .mdc-data-table__header-cell { + overflow: hidden; + position: relative; + } + .mdc-data-table__header-cell span { + position: relative; + left: 0px; + } + :host([dir="rtl"]) .mdc-data-table__header-cell span { + left: auto; + right: 0px; + } - .mdc-data-table__header-cell.sortable { - cursor: pointer; - } - .mdc-data-table__header-cell > * { - transition: left 0.2s ease; - } - :host([dir="rtl"]) .mdc-data-table__header-cell > * { - transition: right 0.2s ease; - } - .mdc-data-table__header-cell ha-icon { - top: -3px; - position: absolute; - } - .mdc-data-table__header-cell.not-sorted ha-icon { - left: -20px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-icon { - right: -20px; - } - .mdc-data-table__header-cell.sortable:not(.not-sorted) span, - .mdc-data-table__header-cell.sortable.not-sorted:hover span { - left: 24px; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable:not(.not-sorted) - span, - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable.not-sorted:hover - span { - left: auto; - right: 24px; - } - .mdc-data-table__header-cell.sortable:not(.not-sorted) ha-icon, - .mdc-data-table__header-cell.sortable:hover.not-sorted ha-icon { - left: 12px; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable:not(.not-sorted) - ha-icon, - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable:hover.not-sorted - ha-icon { - left: auto; - right: 12px; - } - .table-header { - border-bottom: 1px solid var(--divider-color); - padding: 0 16px; - } - search-input { - position: relative; - top: 2px; - } - slot[name="header"] { - display: block; - } - .center { - text-align: center; - } - .secondary { - color: var(--secondary-text-color); - } - .scroller { - display: flex; - position: relative; - contain: strict; - height: calc(100% - 57px); - } - .mdc-data-table__table:not(.auto-height) .scroller { - overflow: auto; - } - .grows { - flex-grow: 1; - flex-shrink: 1; - } - .forceLTR { - direction: ltr; - } - .clickable { - cursor: pointer; - } - `; + .mdc-data-table__header-cell.sortable { + cursor: pointer; + } + .mdc-data-table__header-cell > * { + transition: left 0.2s ease; + } + :host([dir="rtl"]) .mdc-data-table__header-cell > * { + transition: right 0.2s ease; + } + .mdc-data-table__header-cell ha-icon { + top: -3px; + position: absolute; + } + .mdc-data-table__header-cell.not-sorted ha-icon { + left: -20px; + } + :host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-icon { + right: -20px; + } + .mdc-data-table__header-cell.sortable:not(.not-sorted) span, + .mdc-data-table__header-cell.sortable.not-sorted:hover span { + left: 24px; + } + :host([dir="rtl"]) + .mdc-data-table__header-cell.sortable:not(.not-sorted) + span, + :host([dir="rtl"]) + .mdc-data-table__header-cell.sortable.not-sorted:hover + span { + left: auto; + right: 24px; + } + .mdc-data-table__header-cell.sortable:not(.not-sorted) ha-icon, + .mdc-data-table__header-cell.sortable:hover.not-sorted ha-icon { + left: 12px; + } + :host([dir="rtl"]) + .mdc-data-table__header-cell.sortable:not(.not-sorted) + ha-icon, + :host([dir="rtl"]) + .mdc-data-table__header-cell.sortable:hover.not-sorted + ha-icon { + left: auto; + right: 12px; + } + .table-header { + border-bottom: 1px solid var(--divider-color); + padding: 0 16px; + } + search-input { + position: relative; + top: 2px; + } + slot[name="header"] { + display: block; + } + .center { + text-align: center; + } + .secondary { + color: var(--secondary-text-color); + } + .scroller { + display: flex; + position: relative; + contain: strict; + height: calc(100% - 57px); + } + .mdc-data-table__table:not(.auto-height) .scroller { + overflow: auto; + } + .grows { + flex-grow: 1; + flex-shrink: 1; + } + .forceLTR { + direction: ltr; + } + .clickable { + cursor: pointer; + } + `, + ]; } } diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 2b9687b20d..179b221515 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -11,6 +11,7 @@ import { query, TemplateResult, } from "lit-element"; +import { LocalizeFunc } from "../common/translations/localize"; import { fireEvent } from "../common/dom/fire_event"; import { computeRTLDirection } from "../common/util/compute_rtl"; import "../components/data-table/ha-data-table"; @@ -35,6 +36,8 @@ declare global { export class HaTabsSubpageDataTable extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public localizeFunc?: LocalizeFunc; + @property({ type: Boolean }) public isWide = false; @property({ type: Boolean, reflect: true }) public narrow = false; @@ -185,6 +188,7 @@ export class HaTabsSubpageDataTable extends LitElement { return html`