diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts index ff2b5f9221..bcf6ed0e4a 100644 --- a/hassio/src/ingress-view/hassio-ingress-view.ts +++ b/hassio/src/ingress-view/hassio-ingress-view.ts @@ -26,21 +26,15 @@ class HassioIngressView extends LitElement { protected render(): TemplateResult | void { if (!this._addon) { return html` - + `; } - const iframe = html` - + return html` + + + `; - - return location.search === "?kiosk" - ? iframe - : html` - - ${iframe} - - `; } protected updated(changedProps: PropertyValues) { diff --git a/hassio/src/snapshots/hassio-snapshots.js b/hassio/src/snapshots/hassio-snapshots.js deleted file mode 100644 index 2ea513b4a7..0000000000 --- a/hassio/src/snapshots/hassio-snapshots.js +++ /dev/null @@ -1,316 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/paper-card/paper-card"; -import "@polymer/paper-checkbox/paper-checkbox"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-radio-button/paper-radio-button"; -import "@polymer/paper-radio-group/paper-radio-group"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -import "../components/hassio-card-content"; -import "../resources/hassio-style"; -import EventsMixin from "../../../src/mixins/events-mixin"; - -import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; - -class HassioSnapshots extends EventsMixin(PolymerElement) { - static get template() { - return html` - -
-
-
- Create snapshot -
- Snapshots allow you to easily backup and restore all data of your - Hass.io instance. -
-
- -
- - Type: - - - Full snapshot - - - Partial snapshot - - - - Security: - Password protection - - -
-
- Create -
-
-
- -
-
Available snapshots
- - -
-
- `; - } - - static get properties() { - return { - hass: Object, - snapshotName: { - type: String, - value: "", - }, - snapshotPassword: { - type: String, - value: "", - }, - snapshotHasPassword: Boolean, - snapshotType: { - type: String, - value: "full", - }, - snapshots: { - type: Array, - value: [], - }, - supervisorInfo: Object, - installedAddons: { - type: Array, - computed: "_computeAddons(supervisorInfo)", - observer: "_installedAddonsChanged", - }, - addonList: Array, - folderList: { - type: Array, - value: [ - { - slug: "homeassistant", - name: "Home Assistant configuration", - checked: true, - }, - { slug: "ssl", name: "SSL", checked: true }, - { slug: "share", name: "Share", checked: true }, - { slug: "addons/local", name: "Local add-ons", checked: true }, - ], - }, - snapshotSlug: { - type: String, - notify: true, - }, - snapshotDeleted: { - type: Boolean, - notify: true, - observer: "_snapshotDeletedChanged", - }, - creatingSnapshot: Boolean, - dialogOpened: Boolean, - error: String, - }; - } - - ready() { - super.ready(); - this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev)); - this._updateSnapshots(); - } - - _apiCalled(ev) { - if (ev.detail.success) { - this._updateSnapshots(); - } - } - - _updateSnapshots() { - this.hass.callApi("get", "hassio/snapshots").then( - (result) => { - this.snapshots = result.data.snapshots; - }, - (error) => { - this.error = error.message; - } - ); - } - - _createSnapshot() { - this.error = ""; - if (this.snapshotHasPassword && !this.snapshotPassword.length) { - this.error = "Please enter a password."; - return; - } - this.creatingSnapshot = true; - let name = this.snapshotName; - if (!name.length) { - name = new Date().toLocaleDateString(navigator.language, { - weekday: "long", - year: "numeric", - month: "short", - day: "numeric", - }); - } - let data; - let path; - if (this.snapshotType === "full") { - data = { name: name }; - path = "hassio/snapshots/new/full"; - } 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); - - data = { name: name, folders: folders, addons: addons }; - path = "hassio/snapshots/new/partial"; - } - if (this.snapshotHasPassword) { - data.password = this.snapshotPassword; - } - - this.hass.callApi("post", path, data).then( - () => { - this.creatingSnapshot = false; - this.fire("hass-api-called", { success: true }); - }, - (error) => { - this.creatingSnapshot = false; - this.error = error.message; - } - ); - } - - _installedAddonsChanged(addons) { - this.addonList = addons.map((addon) => ({ - slug: addon.slug, - name: addon.name, - checked: true, - })); - } - - _sortAddons(a, b) { - return a.name < b.name ? -1 : 1; - } - - _sortSnapshots(a, b) { - return a.date < b.date ? 1 : -1; - } - - _computeName(snapshot) { - return snapshot.name || snapshot.slug; - } - - _computeDetails(snapshot) { - const type = - snapshot.type === "full" ? "Full snapshot" : "Partial snapshot"; - return snapshot.protected ? `${type}, password protected` : type; - } - - _computeIcon(type) { - return type === "full" - ? "hassio:package-variant-closed" - : "hassio:package-variant"; - } - - _snapshotClicked(ev) { - showHassioSnapshotDialog(this, { - slug: ev.model.snapshot.slug, - onDelete: () => this._updateSnapshots(), - }); - this.snapshotSlug = ev.model.snapshot.slug; - } - - _fullSelected(type) { - return type === "full"; - } - - refreshData() { - this.hass.callApi("post", "hassio/snapshots/reload").then(() => { - this._updateSnapshots(); - }); - } - - _computeAddons(supervisorInfo) { - return supervisorInfo.addons; - } -} - -customElements.define("hassio-snapshots", HassioSnapshots); diff --git a/hassio/src/snapshots/hassio-snapshots.ts b/hassio/src/snapshots/hassio-snapshots.ts new file mode 100644 index 0000000000..73cc92bd5c --- /dev/null +++ b/hassio/src/snapshots/hassio-snapshots.ts @@ -0,0 +1,363 @@ +import { + LitElement, + TemplateResult, + html, + CSSResultArray, + css, + property, + PropertyValues, + customElement, +} from "lit-element"; +import "@material/mwc-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-checkbox/paper-checkbox"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-radio-button/paper-radio-button"; +import "@polymer/paper-radio-group/paper-radio-group"; + +import "../components/hassio-card-content"; +import { hassioStyle } from "../resources/hassio-style"; + +import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; +import { HomeAssistant } from "../../../src/types"; +import { + HassioSnapshot, + HassioSupervisorInfo, + fetchHassioSnapshots, + reloadHassioSnapshots, + HassioFullSnapshotCreateParams, + HassioPartialSnapshotCreateParams, + createHassioFullSnapshot, + createHassioPartialSnapshot, +} from "../../../src/data/hassio"; +import { PolymerChangedEvent } from "../../../src/polymer-types"; +import { fireEvent } from "../../../src/common/dom/fire_event"; + +// Not duplicate, used for typing +// tslint:disable-next-line +import { PaperInputElement } from "@polymer/paper-input/paper-input"; +// tslint:disable-next-line +import { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group"; +// tslint:disable-next-line +import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; + +interface CheckboxItem { + slug: string; + name: string; + checked: boolean; +} + +@customElement("hassio-snapshots") +class HassioSnapshots extends LitElement { + @property() public hass!: HomeAssistant; + @property() public supervisorInfo!: HassioSupervisorInfo; + @property() private _snapshotName = ""; + @property() private _snapshotPassword = ""; + @property() private _snapshotHasPassword = false; + @property() private _snapshotType: HassioSnapshot["type"] = "full"; + @property() private _snapshots?: HassioSnapshot[] = []; + @property() private _addonList: CheckboxItem[] = []; + @property() private _folderList: CheckboxItem[] = [ + { + slug: "homeassistant", + name: "Home Assistant configuration", + checked: true, + }, + { slug: "ssl", name: "SSL", checked: true }, + { slug: "share", name: "Share", checked: true }, + { slug: "addons/local", name: "Local add-ons", checked: true }, + ]; + @property() private _creatingSnapshot = false; + @property() private _error = ""; + + public async refreshData() { + await reloadHassioSnapshots(this.hass); + await this._updateSnapshots(); + } + + protected render(): TemplateResult | void { + return html` +
+
+
+ Create snapshot +
+ Snapshots allow you to easily backup and restore all data of your + Hass.io instance. +
+
+ +
+ + Type: + + + Full snapshot + + + Partial snapshot + + + ${this._snapshotType === "full" + ? undefined + : html` + Folders: + ${this._folderList.map( + (folder, idx) => html` + + ${folder.name} + + ` + )} + Add-ons: + ${this._addonList.map( + (addon, idx) => html` + + ${addon.name} + + ` + )} + `} + Security: + + Password protection + + ${this._snapshotHasPassword + ? html` + + ` + : undefined} + ${this._error !== "" + ? html` +

${this._error}

+ ` + : undefined} +
+
+ + Create + +
+
+
+ +
+
Available snapshots
+ ${this._snapshots === undefined + ? undefined + : this._snapshots.length === 0 + ? html` + +
+ You don't have any snapshots yet. +
+
+ ` + : this._snapshots.map( + (snapshot) => html` + +
+ +
+
+ ` + )} +
+
+ `; + } + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + this._updateSnapshots(); + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("supervisorInfo")) { + this._addonList = this.supervisorInfo.addons + .map((addon) => ({ + slug: addon.slug, + name: addon.name, + checked: true, + })) + .sort((a, b) => (a.name < b.name ? -1 : 1)); + } + } + + 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 = err.message; + } + } + + private async _createSnapshot() { + this._error = ""; + if (this._snapshotHasPassword && !this._snapshotPassword.length) { + this._error = "Please enter a password."; + return; + } + this._creatingSnapshot = true; + 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(); + fireEvent(this, "hass-api-called", { success: true, response: null }); + } catch (err) { + this._error = err.message; + } finally { + this._creatingSnapshot = false; + } + } + + private _computeDetails(snapshot: HassioSnapshot) { + const type = + snapshot.type === "full" ? "Full snapshot" : "Partial snapshot"; + return snapshot.protected ? `${type}, password protected` : type; + } + + private _snapshotClicked(ev) { + showHassioSnapshotDialog(this, { + slug: ev.currentTarget!.snapshot.slug, + onDelete: () => this._updateSnapshots(), + }); + } + + static get styles(): CSSResultArray { + return [ + 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; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hassio-snapshots": HassioSnapshots; + } +} diff --git a/hassio/webpack.config.js b/hassio/webpack.config.js index be7ff0a5e7..1a8936d771 100644 --- a/hassio/webpack.config.js +++ b/hassio/webpack.config.js @@ -33,6 +33,7 @@ module.exports = { }, optimization: { ...webpackBase.optimization(latestBuild), + // Try #4323432 to get hass.io to wrk on es5 concatenateModules: false, }, plugins: [ diff --git a/src/data/hassio.ts b/src/data/hassio.ts index cd680c4602..3066bf31cf 100644 --- a/src/data/hassio.ts +++ b/src/data/hassio.ts @@ -102,6 +102,25 @@ export type HassioHomeAssistantInfo = any; export type HassioSupervisorInfo = any; export type HassioHostInfo = any; +export interface HassioSnapshot { + slug: string; + date: string; + name: string; + type: "full" | "partial"; + protected: boolean; +} + +export interface HassioFullSnapshotCreateParams { + name: string; + password?: string; +} +export interface HassioPartialSnapshotCreateParams { + name: string; + folders: string[]; + addons: string[]; + password?: string; +} + const hassioApiResultExtractor = (response: HassioResponse) => response.data; @@ -116,9 +135,7 @@ export const createHassioSession = async (hass: HomeAssistant) => { }; export const reloadHassioAddons = (hass: HomeAssistant) => - hass - .callApi>("POST", `hassio/addons/reload`) - .then(hassioApiResultExtractor); + hass.callApi("POST", `hassio/addons/reload`); export const fetchHassioAddonsInfo = (hass: HomeAssistant) => hass @@ -153,3 +170,24 @@ export const fetchHassioHomeAssistantInfo = (hass: HomeAssistant) => "hassio/homeassistant/info" ) .then(hassioApiResultExtractor); + +export const fetchHassioSnapshots = (hass: HomeAssistant) => + hass + .callApi>( + "GET", + "hassio/snapshots" + ) + .then((resp) => resp.data.snapshots); + +export const reloadHassioSnapshots = (hass: HomeAssistant) => + hass.callApi("POST", `hassio/snapshots/reload`); + +export const createHassioFullSnapshot = ( + hass: HomeAssistant, + data: HassioFullSnapshotCreateParams +) => hass.callApi("POST", "hassio/snapshots/new/full", data); + +export const createHassioPartialSnapshot = ( + hass: HomeAssistant, + data: HassioPartialSnapshotCreateParams +) => hass.callApi("POST", "hassio/snapshots/new/partial", data); diff --git a/src/polymer-types.ts b/src/polymer-types.ts index e13e45297a..4432149176 100644 --- a/src/polymer-types.ts +++ b/src/polymer-types.ts @@ -28,5 +28,9 @@ declare global { "hass-notification": { message: string; }; + "hass-api-called": { + success: boolean; + response: unknown; + }; } }