mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-10 19:06:36 +00:00
Restore snapshot from onboarding (#7132)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
f48a28264f
commit
590cd8500d
@ -13,7 +13,6 @@ import {
|
|||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/ha-circular-progress";
|
import "../../../src/components/ha-circular-progress";
|
||||||
import "../../../src/components/ha-svg-icon";
|
import "../../../src/components/ha-svg-icon";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
|
||||||
import {
|
import {
|
||||||
HassioSnapshot,
|
HassioSnapshot,
|
||||||
uploadSnapshot,
|
uploadSnapshot,
|
||||||
@ -38,12 +37,12 @@ export class HassioUploadSnapshot extends LitElement {
|
|||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-file-upload
|
<ha-file-upload
|
||||||
.hass=${this.hass}
|
|
||||||
.uploading=${this._uploading}
|
.uploading=${this._uploading}
|
||||||
.icon=${mdiFolderUpload}
|
.icon=${mdiFolderUpload}
|
||||||
accept="application/x-tar"
|
accept="application/x-tar"
|
||||||
label="Upload snapshot"
|
label="Upload snapshot"
|
||||||
@file-picked=${this._uploadFile}
|
@file-picked=${this._uploadFile}
|
||||||
|
auto-open-file-dialog
|
||||||
></ha-file-upload>
|
></ha-file-upload>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -55,6 +54,7 @@ export class HassioUploadSnapshot extends LitElement {
|
|||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: "Unsupported file format",
|
title: "Unsupported file format",
|
||||||
text: "Please choose a Home Assistant snapshot file (.tar)",
|
text: "Please choose a Home Assistant snapshot file (.tar)",
|
||||||
|
confirmText: "ok",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -65,7 +65,8 @@ export class HassioUploadSnapshot extends LitElement {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: "Upload failed",
|
title: "Upload failed",
|
||||||
text: extractApiErrorMessage(err),
|
text: err.toString(),
|
||||||
|
confirmText: "ok",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
this._uploading = false;
|
this._uploading = false;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
|
css,
|
||||||
CSSResult,
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
@ -8,7 +10,7 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
import "../../../../src/components/ha-header-bar";
|
||||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
@ -30,7 +32,11 @@ export class DialogHassioSnapshotUpload extends LitElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this._params?.reloadSnapshot();
|
if (this._params && !this._params.onboarding) {
|
||||||
|
if (this._params.reloadSnapshot) {
|
||||||
|
this._params.reloadSnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
@ -46,9 +52,19 @@ export class DialogHassioSnapshotUpload extends LitElement
|
|||||||
scrimClickAction
|
scrimClickAction
|
||||||
escapeKeyAction
|
escapeKeyAction
|
||||||
hideActions
|
hideActions
|
||||||
|
.heading=${true}
|
||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${createCloseHeading(this.hass, "Upload snapshot")}
|
|
||||||
>
|
>
|
||||||
|
<div slot="heading">
|
||||||
|
<ha-header-bar>
|
||||||
|
<span slot="title">
|
||||||
|
Upload snapshot
|
||||||
|
</span>
|
||||||
|
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||||
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</ha-header-bar>
|
||||||
|
</div>
|
||||||
<hassio-upload-snapshot
|
<hassio-upload-snapshot
|
||||||
@snapshot-uploaded=${this._snapshotUploaded}
|
@snapshot-uploaded=${this._snapshotUploaded}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -63,8 +79,24 @@ export class DialogHassioSnapshotUpload extends LitElement
|
|||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult[] {
|
||||||
return haStyleDialog;
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-header-bar {
|
||||||
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
/* overrule the ha-style-dialog max-height on small screens */
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
ha-header-bar {
|
||||||
|
--mdc-theme-primary: var(--app-header-background-color);
|
||||||
|
--mdc-theme-on-primary: var(--app-header-text-color, white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
|
import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
|
||||||
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import {
|
import {
|
||||||
@ -12,7 +12,8 @@ import {
|
|||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
|
import "../../../../src/components/ha-header-bar";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
import { getSignedPath } from "../../../../src/data/auth";
|
import { getSignedPath } from "../../../../src/data/auth";
|
||||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
@ -22,7 +23,7 @@ import {
|
|||||||
} from "../../../../src/data/hassio/snapshot";
|
} from "../../../../src/data/hassio/snapshot";
|
||||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||||
import { PolymerChangedEvent } from "../../../../src/polymer-types";
|
import { PolymerChangedEvent } from "../../../../src/polymer-types";
|
||||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||||
|
|
||||||
@ -75,6 +76,8 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _error?: string;
|
@internalProperty() private _error?: string;
|
||||||
|
|
||||||
|
@internalProperty() private _onboarding = false;
|
||||||
|
|
||||||
@internalProperty() private _snapshot?: HassioSnapshotDetail;
|
@internalProperty() private _snapshot?: HassioSnapshotDetail;
|
||||||
|
|
||||||
@internalProperty() private _folders!: FolderItem[];
|
@internalProperty() private _folders!: FolderItem[];
|
||||||
@ -90,13 +93,14 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
public async showDialog(params: HassioSnapshotDialogParams) {
|
public async showDialog(params: HassioSnapshotDialogParams) {
|
||||||
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
||||||
this._folders = _computeFolders(
|
this._folders = _computeFolders(
|
||||||
this._snapshot.folders
|
this._snapshot?.folders
|
||||||
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
|
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
|
||||||
this._addons = _computeAddons(
|
this._addons = _computeAddons(
|
||||||
this._snapshot.addons
|
this._snapshot?.addons
|
||||||
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
|
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
|
||||||
|
|
||||||
this._dialogParams = params;
|
this._dialogParams = params;
|
||||||
|
this._onboarding = params.onboarding ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -104,12 +108,17 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog open stacked @closing=${this._closeDialog} .heading=${true}>
|
||||||
open
|
<div slot="heading">
|
||||||
stacked
|
<ha-header-bar>
|
||||||
@closing=${this._closeDialog}
|
<span slot="title">
|
||||||
.heading=${createCloseHeading(this.hass, this._computeName)}
|
${this._computeName}
|
||||||
>
|
</span>
|
||||||
|
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||||
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</ha-header-bar>
|
||||||
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
${this._snapshot.type === "full"
|
${this._snapshot.type === "full"
|
||||||
? "Full snapshot"
|
? "Full snapshot"
|
||||||
@ -182,11 +191,15 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
|
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
|
||||||
|
|
||||||
<div>Actions:</div>
|
<div>Actions:</div>
|
||||||
|
${!this._onboarding
|
||||||
<mwc-button @click=${this._downloadClicked} slot="primaryAction">
|
? html`<mwc-button
|
||||||
|
@click=${this._downloadClicked}
|
||||||
|
slot="primaryAction"
|
||||||
|
>
|
||||||
<ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
|
<ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
|
||||||
Download Snapshot
|
Download Snapshot
|
||||||
</mwc-button>
|
</mwc-button>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._partialRestoreClicked}
|
@click=${this._partialRestoreClicked}
|
||||||
@ -206,16 +219,22 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<mwc-button @click=${this._deleteClicked} slot="secondaryAction">
|
${!this._onboarding
|
||||||
|
? html`<mwc-button
|
||||||
|
@click=${this._deleteClicked}
|
||||||
|
slot="secondaryAction"
|
||||||
|
>
|
||||||
<ha-svg-icon path=${mdiDelete} class="icon warning"></ha-svg-icon>
|
<ha-svg-icon path=${mdiDelete} class="icon warning"></ha-svg-icon>
|
||||||
<span class="warning">Delete Snapshot</span>
|
<span class="warning">Delete Snapshot</span>
|
||||||
</mwc-button>
|
</mwc-button>`
|
||||||
|
: ""}
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-checkbox {
|
paper-checkbox {
|
||||||
@ -242,6 +261,18 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
.no-margin-top {
|
.no-margin-top {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
ha-header-bar {
|
||||||
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
/* overrule the ha-style-dialog max-height on small screens */
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
ha-header-bar {
|
||||||
|
--mdc-theme-primary: var(--app-header-background-color);
|
||||||
|
--mdc-theme-on-primary: var(--app-header-text-color, white);
|
||||||
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -272,6 +303,8 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
if (
|
if (
|
||||||
!(await showConfirmationDialog(this, {
|
!(await showConfirmationDialog(this, {
|
||||||
title: "Are you sure you want partially to restore this snapshot?",
|
title: "Are you sure you want partially to restore this snapshot?",
|
||||||
|
confirmText: "restore",
|
||||||
|
dismissText: "cancel",
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@ -300,6 +333,7 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
data.password = this._snapshotPassword;
|
data.password = this._snapshotPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._onboarding) {
|
||||||
this.hass
|
this.hass
|
||||||
.callApi(
|
.callApi(
|
||||||
"POST",
|
"POST",
|
||||||
@ -316,6 +350,14 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
this._error = error.body.message;
|
this._error = error.body.message;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
fireEvent(this, "restoring");
|
||||||
|
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
this._closeDialog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fullRestoreClicked() {
|
private async _fullRestoreClicked() {
|
||||||
@ -323,6 +365,8 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
!(await showConfirmationDialog(this, {
|
!(await showConfirmationDialog(this, {
|
||||||
title:
|
title:
|
||||||
"Are you sure you want to wipe your system and restore this snapshot?",
|
"Are you sure you want to wipe your system and restore this snapshot?",
|
||||||
|
confirmText: "restore",
|
||||||
|
dismissText: "cancel",
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@ -331,7 +375,7 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
const data = this._snapshot!.protected
|
const data = this._snapshot!.protected
|
||||||
? { password: this._snapshotPassword }
|
? { password: this._snapshotPassword }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
if (!this._onboarding) {
|
||||||
this.hass
|
this.hass
|
||||||
.callApi(
|
.callApi(
|
||||||
"POST",
|
"POST",
|
||||||
@ -347,12 +391,22 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
this._error = error.body.message;
|
this._error = error.body.message;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
fireEvent(this, "restoring");
|
||||||
|
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
this._closeDialog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteClicked() {
|
private async _deleteClicked() {
|
||||||
if (
|
if (
|
||||||
!(await showConfirmationDialog(this, {
|
!(await showConfirmationDialog(this, {
|
||||||
title: "Are you sure you want to delete this snapshot?",
|
title: "Are you sure you want to delete this snapshot?",
|
||||||
|
confirmText: "delete",
|
||||||
|
dismissText: "cancel",
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@ -363,7 +417,9 @@ class HassioSnapshotDialog extends LitElement {
|
|||||||
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
|
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
|
if (this._dialogParams!.onDelete) {
|
||||||
this._dialogParams!.onDelete();
|
this._dialogParams!.onDelete();
|
||||||
|
}
|
||||||
this._closeDialog();
|
this._closeDialog();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
@ -2,7 +2,8 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
|||||||
|
|
||||||
export interface HassioSnapshotDialogParams {
|
export interface HassioSnapshotDialogParams {
|
||||||
slug: string;
|
slug: string;
|
||||||
onDelete: () => void;
|
onDelete?: () => void;
|
||||||
|
onboarding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showHassioSnapshotDialog = (
|
export const showHassioSnapshotDialog = (
|
||||||
|
@ -3,7 +3,8 @@ import "./dialog-hassio-snapshot-upload";
|
|||||||
|
|
||||||
export interface HassioSnapshotUploadDialogParams {
|
export interface HassioSnapshotUploadDialogParams {
|
||||||
showSnapshot: (slug: string) => void;
|
showSnapshot: (slug: string) => void;
|
||||||
reloadSnapshot: () => Promise<void>;
|
reloadSnapshot?: () => Promise<void>;
|
||||||
|
onboarding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showSnapshotUploadDialog = (
|
export const showSnapshotUploadDialog = (
|
||||||
|
@ -10,11 +10,11 @@ import {
|
|||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
query,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
import "./ha-circular-progress";
|
import "./ha-circular-progress";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
@ -26,8 +26,6 @@ declare global {
|
|||||||
|
|
||||||
@customElement("ha-file-upload")
|
@customElement("ha-file-upload")
|
||||||
export class HaFileUpload extends LitElement {
|
export class HaFileUpload extends LitElement {
|
||||||
public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public accept!: string;
|
@property() public accept!: string;
|
||||||
|
|
||||||
@property() public icon!: string;
|
@property() public icon!: string;
|
||||||
@ -38,8 +36,20 @@ export class HaFileUpload extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) private uploading = false;
|
@property({ type: Boolean }) private uploading = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "auto-open-file-dialog" })
|
||||||
|
private autoOpenFileDialog = false;
|
||||||
|
|
||||||
@internalProperty() private _drag = false;
|
@internalProperty() private _drag = false;
|
||||||
|
|
||||||
|
@query("#input") private _input?: HTMLInputElement;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
if (this.autoOpenFileDialog) {
|
||||||
|
this._input?.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
if (changedProperties.has("_drag") && !this.uploading) {
|
if (changedProperties.has("_drag") && !this.uploading) {
|
||||||
(this.shadowRoot!.querySelector(
|
(this.shadowRoot!.querySelector(
|
||||||
|
@ -41,7 +41,6 @@ export class HaPictureUpload extends LitElement {
|
|||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-file-upload
|
<ha-file-upload
|
||||||
.hass=${this.hass}
|
|
||||||
.icon=${mdiImagePlus}
|
.icon=${mdiImagePlus}
|
||||||
.label=${this.label ||
|
.label=${this.label ||
|
||||||
this.hass.localize("ui.components.picture-upload.label")}
|
this.hass.localize("ui.components.picture-upload.label")}
|
||||||
|
17
src/data/discovery.ts
Normal file
17
src/data/discovery.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export interface DiscoveryInformation {
|
||||||
|
uuid: string;
|
||||||
|
base_url: string | null;
|
||||||
|
external_url: string | null;
|
||||||
|
internal_url: string | null;
|
||||||
|
location_name: string;
|
||||||
|
installation_type: string;
|
||||||
|
requires_api_password: boolean;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchDiscoveryInformation = async (): Promise<
|
||||||
|
DiscoveryInformation
|
||||||
|
> => {
|
||||||
|
const response = await fetch("/api/discovery_info", { method: "GET" });
|
||||||
|
return await response.json();
|
||||||
|
};
|
@ -46,12 +46,20 @@ export const fetchHassioSnapshotInfo = async (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
snapshot: string
|
snapshot: string
|
||||||
) => {
|
) => {
|
||||||
|
if (hass) {
|
||||||
return hassioApiResultExtractor(
|
return hassioApiResultExtractor(
|
||||||
await hass.callApi<HassioResponse<HassioSnapshotDetail>>(
|
await hass.callApi<HassioResponse<HassioSnapshotDetail>>(
|
||||||
"GET",
|
"GET",
|
||||||
`hassio/snapshots/${snapshot}/info`
|
`hassio/snapshots/${snapshot}/info`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
// When called from onboarding we don't have hass
|
||||||
|
const resp = await fetch(`/api/hassio/snapshots/${snapshot}/info`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
const data = (await resp.json()).data;
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reloadHassioSnapshots = async (hass: HomeAssistant) => {
|
export const reloadHassioSnapshots = async (hass: HomeAssistant) => {
|
||||||
@ -85,15 +93,25 @@ export const uploadSnapshot = async (
|
|||||||
file: File
|
file: File
|
||||||
): Promise<HassioResponse<HassioSnapshot>> => {
|
): Promise<HassioResponse<HassioSnapshot>> => {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
|
let resp;
|
||||||
fd.append("file", file);
|
fd.append("file", file);
|
||||||
const resp = await hass.fetchWithAuth("/api/hassio/snapshots/new/upload", {
|
if (hass) {
|
||||||
|
resp = await hass.fetchWithAuth("/api/hassio/snapshots/new/upload", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: fd,
|
body: fd,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// When called from onboarding we don't have hass
|
||||||
|
resp = await fetch("/api/hassio/snapshots/new/upload", {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (resp.status === 413) {
|
if (resp.status === 413) {
|
||||||
throw new Error("Uploaded snapshot is too large");
|
throw new Error("Uploaded snapshot is too large");
|
||||||
} else if (resp.status !== 200) {
|
} else if (resp.status !== 200) {
|
||||||
throw new Error("Unknown error");
|
throw new Error(`${resp.status} ${resp.statusText}`);
|
||||||
}
|
}
|
||||||
return await resp.json();
|
return await resp.json();
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
Auth,
|
Auth,
|
||||||
createConnection,
|
createConnection,
|
||||||
|
genClientId,
|
||||||
getAuth,
|
getAuth,
|
||||||
subscribeConfig,
|
subscribeConfig,
|
||||||
genClientId,
|
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
property,
|
|
||||||
internalProperty,
|
internalProperty,
|
||||||
|
property,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||||
|
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||||
import { subscribeOne } from "../common/util/subscribe-one";
|
import { subscribeOne } from "../common/util/subscribe-one";
|
||||||
import { hassUrl, AuthUrlSearchParams } from "../data/auth";
|
import { AuthUrlSearchParams, hassUrl } from "../data/auth";
|
||||||
|
import { fetchDiscoveryInformation } from "../data/discovery";
|
||||||
import {
|
import {
|
||||||
fetchOnboardingOverview,
|
fetchOnboardingOverview,
|
||||||
OnboardingResponses,
|
OnboardingResponses,
|
||||||
@ -29,7 +31,6 @@ import { HomeAssistant } from "../types";
|
|||||||
import { registerServiceWorker } from "../util/register-service-worker";
|
import { registerServiceWorker } from "../util/register-service-worker";
|
||||||
import "./onboarding-create-user";
|
import "./onboarding-create-user";
|
||||||
import "./onboarding-loading";
|
import "./onboarding-loading";
|
||||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
|
||||||
|
|
||||||
type OnboardingEvent =
|
type OnboardingEvent =
|
||||||
| {
|
| {
|
||||||
@ -62,6 +63,10 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
|
|
||||||
@internalProperty() private _loading = false;
|
@internalProperty() private _loading = false;
|
||||||
|
|
||||||
|
@internalProperty() private _restoring = false;
|
||||||
|
|
||||||
|
@internalProperty() private _supervisor?: boolean;
|
||||||
|
|
||||||
@internalProperty() private _steps?: OnboardingStep[];
|
@internalProperty() private _steps?: OnboardingStep[];
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -72,10 +77,21 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
}
|
}
|
||||||
if (step.step === "user") {
|
if (step.step === "user") {
|
||||||
return html`
|
return html`
|
||||||
<onboarding-create-user
|
${!this._restoring
|
||||||
|
? html`<onboarding-create-user
|
||||||
.localize=${this.localize}
|
.localize=${this.localize}
|
||||||
.language=${this.language}
|
.language=${this.language}
|
||||||
></onboarding-create-user>
|
>
|
||||||
|
</onboarding-create-user>`
|
||||||
|
: ""}
|
||||||
|
${this._supervisor
|
||||||
|
? html`<onboarding-restore-snapshot
|
||||||
|
.localize=${this.localize}
|
||||||
|
.restoring=${this._restoring}
|
||||||
|
@restoring=${this._restoringSnapshot}
|
||||||
|
>
|
||||||
|
</onboarding-restore-snapshot>`
|
||||||
|
: ""}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
if (step.step === "core_config") {
|
if (step.step === "core_config") {
|
||||||
@ -100,6 +116,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._fetchOnboardingSteps();
|
this._fetchOnboardingSteps();
|
||||||
|
this._fetchDiscoveryInformation();
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"
|
/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"
|
||||||
);
|
);
|
||||||
@ -127,6 +144,32 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
return this._steps ? this._steps.find((stp) => !stp.done) : undefined;
|
return this._steps ? this._steps.find((stp) => !stp.done) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _restoringSnapshot() {
|
||||||
|
this._restoring = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchDiscoveryInformation(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetchDiscoveryInformation();
|
||||||
|
this._supervisor = [
|
||||||
|
"Home Assistant OS",
|
||||||
|
"Home Assistant Supervised",
|
||||||
|
].includes(response.installation_type);
|
||||||
|
if (this._supervisor) {
|
||||||
|
// Only load if we have supervisor
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "onboarding-restore-snapshot" */ "./onboarding-restore-snapshot"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
"Something went wrong loading onboarding-restore-snapshot",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _fetchOnboardingSteps() {
|
private async _fetchOnboardingSteps() {
|
||||||
try {
|
try {
|
||||||
const response = await (window.stepsPromise || fetchOnboardingOverview());
|
const response = await (window.stepsPromise || fetchOnboardingOverview());
|
||||||
|
172
src/onboarding/onboarding-restore-snapshot.ts
Normal file
172
src/onboarding/onboarding-restore-snapshot.ts
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
internalProperty,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../../hassio/src/components/hassio-ansi-to-html";
|
||||||
|
import { showHassioSnapshotDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||||
|
import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show-dialog-snapshot-upload";
|
||||||
|
import { navigate } from "../common/navigate";
|
||||||
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
|
import "../components/ha-card";
|
||||||
|
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||||
|
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
||||||
|
import { haStyle } from "../resources/styles";
|
||||||
|
import "./onboarding-loading";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
restoring: undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("onboarding-restore-snapshot")
|
||||||
|
class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
||||||
|
@property() public localize!: LocalizeFunc;
|
||||||
|
|
||||||
|
@property() public language!: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) private restoring = false;
|
||||||
|
|
||||||
|
@internalProperty() private _log?: string;
|
||||||
|
|
||||||
|
@internalProperty() private _showFullLog = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return this.restoring
|
||||||
|
? html`<ha-card
|
||||||
|
.header=${this.localize(
|
||||||
|
"ui.panel.page-onboarding.restore.in_progress"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
${this._log
|
||||||
|
? this._showFullLog
|
||||||
|
? html`<hassio-ansi-to-html .content=${this._log}>
|
||||||
|
</hassio-ansi-to-html>`
|
||||||
|
: html`<onboarding-loading></onboarding-loading>
|
||||||
|
<hassio-ansi-to-html
|
||||||
|
class="logentry"
|
||||||
|
.content=${this._lastLogEntry(this._log)}
|
||||||
|
>
|
||||||
|
</hassio-ansi-to-html>`
|
||||||
|
: ""}
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button @click=${this._toggeFullLog}>
|
||||||
|
${this._showFullLog
|
||||||
|
? this.localize("ui.panel.page-onboarding.restore.hide_log")
|
||||||
|
: this.localize("ui.panel.page-onboarding.restore.show_log")}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>`
|
||||||
|
: html`
|
||||||
|
<button class="link" @click=${this._uploadSnapshot}>
|
||||||
|
${this.localize("ui.panel.page-onboarding.restore.description")}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggeFullLog(): void {
|
||||||
|
this._showFullLog = !this._showFullLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterLogs(logs: string): string {
|
||||||
|
// Filter out logs that is not relevant to show during the restore
|
||||||
|
return logs
|
||||||
|
.split("\n")
|
||||||
|
.filter(
|
||||||
|
(entry) =>
|
||||||
|
!entry.includes("/supervisor/logs") &&
|
||||||
|
!entry.includes("/supervisor/ping") &&
|
||||||
|
!entry.includes("DEBUG")
|
||||||
|
)
|
||||||
|
.join("\n")
|
||||||
|
.replace(/\s[A-Z]+\s\(\w+\)\s\[[\w.]+\]/gi, "")
|
||||||
|
.replace(/\d{2}-\d{2}-\d{2}\s/gi, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lastLogEntry(logs: string): string {
|
||||||
|
return logs
|
||||||
|
.split("\n")
|
||||||
|
.slice(-2)[0]
|
||||||
|
.replace(/\d{2}:\d{2}:\d{2}\s/gi, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _uploadSnapshot(): void {
|
||||||
|
showSnapshotUploadDialog(this, {
|
||||||
|
showSnapshot: (slug: string) => this._showSnapshotDialog(slug),
|
||||||
|
onboarding: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
makeDialogManager(this, this.shadowRoot!);
|
||||||
|
setInterval(() => this._getLogs(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getLogs(): Promise<void> {
|
||||||
|
if (this.restoring) {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/hassio/supervisor/logs", {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
const logs = await response.text();
|
||||||
|
this._log = this._filterLogs(logs);
|
||||||
|
if (this._log.match(/\d{2}:\d{2}:\d{2}\s.*Restore\s\w+\sdone/)) {
|
||||||
|
// The log indicates that the restore done, navigate the user back to base
|
||||||
|
navigate(this, "/", true);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this._log = err.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showSnapshotDialog(slug: string): void {
|
||||||
|
showHassioSnapshotDialog(this, {
|
||||||
|
slug,
|
||||||
|
onboarding: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.logentry {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
padding: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
hassio-ansi-to-html {
|
||||||
|
display: block;
|
||||||
|
line-height: 22px;
|
||||||
|
padding: 0 8px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 600px) {
|
||||||
|
ha-card {
|
||||||
|
width: 600px;
|
||||||
|
margin-left: -100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"onboarding-restore-snapshot": OnboardingRestoreSnapshot;
|
||||||
|
}
|
||||||
|
}
|
@ -2827,6 +2827,12 @@
|
|||||||
"intro": "Devices and services are represented in Home Assistant as integrations. You can set them up now, or do it later from the configuration screen.",
|
"intro": "Devices and services are represented in Home Assistant as integrations. You can set them up now, or do it later from the configuration screen.",
|
||||||
"more_integrations": "More",
|
"more_integrations": "More",
|
||||||
"finish": "Finish"
|
"finish": "Finish"
|
||||||
|
},
|
||||||
|
"restore": {
|
||||||
|
"description": "Alternatively you can restore from a previous snapshot.",
|
||||||
|
"in_progress": "Restore in progress",
|
||||||
|
"show_log": "Show full log",
|
||||||
|
"hide_log": "Hide full log"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"custom": {
|
"custom": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user