mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-09 18:36:35 +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 "../../../src/components/ha-circular-progress";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
HassioSnapshot,
|
||||
uploadSnapshot,
|
||||
@ -38,12 +37,12 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<ha-file-upload
|
||||
.hass=${this.hass}
|
||||
.uploading=${this._uploading}
|
||||
.icon=${mdiFolderUpload}
|
||||
accept="application/x-tar"
|
||||
label="Upload snapshot"
|
||||
@file-picked=${this._uploadFile}
|
||||
auto-open-file-dialog
|
||||
></ha-file-upload>
|
||||
`;
|
||||
}
|
||||
@ -55,6 +54,7 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
showAlertDialog(this, {
|
||||
title: "Unsupported file format",
|
||||
text: "Please choose a Home Assistant snapshot file (.tar)",
|
||||
confirmText: "ok",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -65,7 +65,8 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Upload failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
text: err.toString(),
|
||||
confirmText: "ok",
|
||||
});
|
||||
} finally {
|
||||
this._uploading = false;
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
@ -8,7 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
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 { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@ -30,7 +32,11 @@ export class DialogHassioSnapshotUpload extends LitElement
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params?.reloadSnapshot();
|
||||
if (this._params && !this._params.onboarding) {
|
||||
if (this._params.reloadSnapshot) {
|
||||
this._params.reloadSnapshot();
|
||||
}
|
||||
}
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
@ -46,9 +52,19 @@ export class DialogHassioSnapshotUpload extends LitElement
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${true}
|
||||
@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
|
||||
@snapshot-uploaded=${this._snapshotUploaded}
|
||||
.hass=${this.hass}
|
||||
@ -63,8 +79,24 @@ export class DialogHassioSnapshotUpload extends LitElement
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return haStyleDialog;
|
||||
static get styles(): CSSResult[] {
|
||||
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 { mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
|
||||
import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
|
||||
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
@ -12,7 +12,8 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} 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 { getSignedPath } from "../../../../src/data/auth";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
@ -22,7 +23,7 @@ import {
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
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 { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||
|
||||
@ -75,6 +76,8 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _onboarding = false;
|
||||
|
||||
@internalProperty() private _snapshot?: HassioSnapshotDetail;
|
||||
|
||||
@internalProperty() private _folders!: FolderItem[];
|
||||
@ -90,13 +93,14 @@ class HassioSnapshotDialog extends LitElement {
|
||||
public async showDialog(params: HassioSnapshotDialogParams) {
|
||||
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
||||
this._folders = _computeFolders(
|
||||
this._snapshot.folders
|
||||
this._snapshot?.folders
|
||||
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
|
||||
this._addons = _computeAddons(
|
||||
this._snapshot.addons
|
||||
this._snapshot?.addons
|
||||
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
|
||||
|
||||
this._dialogParams = params;
|
||||
this._onboarding = params.onboarding ?? false;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@ -104,12 +108,17 @@ class HassioSnapshotDialog extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
stacked
|
||||
@closing=${this._closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this._computeName)}
|
||||
>
|
||||
<ha-dialog open stacked @closing=${this._closeDialog} .heading=${true}>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">
|
||||
${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">
|
||||
${this._snapshot.type === "full"
|
||||
? "Full snapshot"
|
||||
@ -182,11 +191,15 @@ class HassioSnapshotDialog extends LitElement {
|
||||
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
|
||||
|
||||
<div>Actions:</div>
|
||||
|
||||
<mwc-button @click=${this._downloadClicked} slot="primaryAction">
|
||||
${!this._onboarding
|
||||
? html`<mwc-button
|
||||
@click=${this._downloadClicked}
|
||||
slot="primaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
|
||||
Download Snapshot
|
||||
</mwc-button>
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
|
||||
<mwc-button
|
||||
@click=${this._partialRestoreClicked}
|
||||
@ -206,16 +219,22 @@ class HassioSnapshotDialog extends LitElement {
|
||||
</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>
|
||||
<span class="warning">Delete Snapshot</span>
|
||||
</mwc-button>
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
paper-checkbox {
|
||||
@ -242,6 +261,18 @@ class HassioSnapshotDialog extends LitElement {
|
||||
.no-margin-top {
|
||||
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 (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want partially to restore this snapshot?",
|
||||
confirmText: "restore",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
@ -300,6 +333,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
data.password = this._snapshotPassword;
|
||||
}
|
||||
|
||||
if (!this._onboarding) {
|
||||
this.hass
|
||||
.callApi(
|
||||
"POST",
|
||||
@ -316,6 +350,14 @@ class HassioSnapshotDialog extends LitElement {
|
||||
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() {
|
||||
@ -323,6 +365,8 @@ class HassioSnapshotDialog extends LitElement {
|
||||
!(await showConfirmationDialog(this, {
|
||||
title:
|
||||
"Are you sure you want to wipe your system and restore this snapshot?",
|
||||
confirmText: "restore",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
@ -331,7 +375,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
const data = this._snapshot!.protected
|
||||
? { password: this._snapshotPassword }
|
||||
: undefined;
|
||||
|
||||
if (!this._onboarding) {
|
||||
this.hass
|
||||
.callApi(
|
||||
"POST",
|
||||
@ -347,12 +391,22 @@ class HassioSnapshotDialog extends LitElement {
|
||||
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() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want to delete this snapshot?",
|
||||
confirmText: "delete",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
@ -363,7 +417,9 @@ class HassioSnapshotDialog extends LitElement {
|
||||
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
|
||||
.then(
|
||||
() => {
|
||||
if (this._dialogParams!.onDelete) {
|
||||
this._dialogParams!.onDelete();
|
||||
}
|
||||
this._closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
|
@ -2,7 +2,8 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
|
||||
export interface HassioSnapshotDialogParams {
|
||||
slug: string;
|
||||
onDelete: () => void;
|
||||
onDelete?: () => void;
|
||||
onboarding?: boolean;
|
||||
}
|
||||
|
||||
export const showHassioSnapshotDialog = (
|
||||
|
@ -3,7 +3,8 @@ import "./dialog-hassio-snapshot-upload";
|
||||
|
||||
export interface HassioSnapshotUploadDialogParams {
|
||||
showSnapshot: (slug: string) => void;
|
||||
reloadSnapshot: () => Promise<void>;
|
||||
reloadSnapshot?: () => Promise<void>;
|
||||
onboarding?: boolean;
|
||||
}
|
||||
|
||||
export const showSnapshotUploadDialog = (
|
||||
|
@ -10,11 +10,11 @@ import {
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-circular-progress";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@ -26,8 +26,6 @@ declare global {
|
||||
|
||||
@customElement("ha-file-upload")
|
||||
export class HaFileUpload extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property() public accept!: string;
|
||||
|
||||
@property() public icon!: string;
|
||||
@ -38,8 +36,20 @@ export class HaFileUpload extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) private uploading = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "auto-open-file-dialog" })
|
||||
private autoOpenFileDialog = 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) {
|
||||
if (changedProperties.has("_drag") && !this.uploading) {
|
||||
(this.shadowRoot!.querySelector(
|
||||
|
@ -41,7 +41,6 @@ export class HaPictureUpload extends LitElement {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<ha-file-upload
|
||||
.hass=${this.hass}
|
||||
.icon=${mdiImagePlus}
|
||||
.label=${this.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,
|
||||
snapshot: string
|
||||
) => {
|
||||
if (hass) {
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioSnapshotDetail>>(
|
||||
"GET",
|
||||
`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) => {
|
||||
@ -85,15 +93,25 @@ export const uploadSnapshot = async (
|
||||
file: File
|
||||
): Promise<HassioResponse<HassioSnapshot>> => {
|
||||
const fd = new FormData();
|
||||
let resp;
|
||||
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",
|
||||
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) {
|
||||
throw new Error("Uploaded snapshot is too large");
|
||||
} else if (resp.status !== 200) {
|
||||
throw new Error("Unknown error");
|
||||
throw new Error(`${resp.status} ${resp.statusText}`);
|
||||
}
|
||||
return await resp.json();
|
||||
};
|
||||
|
@ -1,21 +1,23 @@
|
||||
import {
|
||||
Auth,
|
||||
createConnection,
|
||||
genClientId,
|
||||
getAuth,
|
||||
subscribeConfig,
|
||||
genClientId,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
internalProperty,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
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 {
|
||||
fetchOnboardingOverview,
|
||||
OnboardingResponses,
|
||||
@ -29,7 +31,6 @@ import { HomeAssistant } from "../types";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./onboarding-create-user";
|
||||
import "./onboarding-loading";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
|
||||
type OnboardingEvent =
|
||||
| {
|
||||
@ -62,6 +63,10 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
|
||||
@internalProperty() private _loading = false;
|
||||
|
||||
@internalProperty() private _restoring = false;
|
||||
|
||||
@internalProperty() private _supervisor?: boolean;
|
||||
|
||||
@internalProperty() private _steps?: OnboardingStep[];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@ -72,10 +77,21 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
}
|
||||
if (step.step === "user") {
|
||||
return html`
|
||||
<onboarding-create-user
|
||||
${!this._restoring
|
||||
? html`<onboarding-create-user
|
||||
.localize=${this.localize}
|
||||
.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") {
|
||||
@ -100,6 +116,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchOnboardingSteps();
|
||||
this._fetchDiscoveryInformation();
|
||||
import(
|
||||
/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"
|
||||
);
|
||||
@ -127,6 +144,32 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
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() {
|
||||
try {
|
||||
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.",
|
||||
"more_integrations": "More",
|
||||
"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": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user