Add add-on restore dialog

This commit is contained in:
Ludeeus 2021-06-03 14:05:39 +00:00
parent bdef9fd040
commit c1df1d41f9
5 changed files with 280 additions and 1 deletions

View File

@ -50,6 +50,10 @@ import {
fetchHassioStats,
HassioStats,
} from "../../../../src/data/hassio/common";
import {
fetchHassioSnapshots,
HassioSnapshot,
} from "../../../../src/data/hassio/snapshot";
import { StoreAddon } from "../../../../src/data/supervisor/store";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import {
@ -61,6 +65,7 @@ import { HomeAssistant } from "../../../../src/types";
import { bytesToString } from "../../../../src/util/bytes-to-string";
import "../../components/hassio-card-content";
import "../../components/supervisor-metric";
import { showHassioAddonRestoreDialog } from "../../dialogs/addon/show-dialog-hassio-addon-restore";
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
import { showDialogSupervisorUpdate } from "../../dialogs/update/show-dialog-update";
import { hassioStyle } from "../../resources/hassio-style";
@ -82,6 +87,8 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public snapshots?: HassioSnapshot[];
@state() private _metrics?: HassioStats;
@state() private _error?: string;
@ -626,6 +633,11 @@ class HassioAddonInfo extends LitElement {
${this.supervisor.localize("addon.dashboard.install")}
</ha-progress-button>
`}
${this.snapshots?.length
? html`<mwc-button @click=${this._restoreClicked}>
${this.supervisor.localize("addon.dashboard.restore")}
</mwc-button>`
: ""}
</div>
<div>
${this.addon.version
@ -698,6 +710,11 @@ class HassioAddonInfo extends LitElement {
}
private async _loadData(): Promise<void> {
const snapshots = await fetchHassioSnapshots(this.hass);
this.snapshots = snapshots.filter((snapshot) =>
snapshot.content.addons.includes(this.addon.slug)
);
if (this.addon.state === "started") {
this._metrics = await fetchHassioStats(
this.hass,
@ -1000,6 +1017,22 @@ class HassioAddonInfo extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
}
private async _restoreClicked(): Promise<void> {
showHassioAddonRestoreDialog(this, {
supervisor: this.supervisor,
snapshots: this.snapshots || [],
addon: this.addon,
onRestore: () => {
const eventdata = {
success: true,
response: undefined,
path: "update",
};
fireEvent(this, "hass-api-called", eventdata);
},
});
}
private async _startClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;

View File

@ -0,0 +1,190 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import relativeTime from "../../../../src/common/datetime/relative_time";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input";
import { compare } from "../../../../src/common/string/compare";
import { nextRender } from "../../../../src/common/util/render-status";
import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-settings-row";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioSnapshotInfo,
HassioPartialSnapshotCreateParams,
HassioSnapshotDetail,
supervisorRestorePartialSnapshot,
} from "../../../../src/data/hassio/snapshot";
import {
showAlertDialog,
showPromptDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonRestoreDialogParams } from "./show-dialog-hassio-addon-restore";
@customElement("dialog-hassio-addon-restore")
class HassioAddonRestoreDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioAddonRestoreDialogParams;
@state() private _snapshots?: HassioSnapshotDetail[];
@state() private _restoring = false;
public showDialog(params: HassioAddonRestoreDialogParams) {
this._dialogParams = params;
this._restoring = false;
Promise.all(
params.snapshots.map((snapshot) =>
fetchHassioSnapshotInfo(this.hass, snapshot.slug)
)
).then((data) => {
this._snapshots = data.sort((a, b) => compare(b.date, a.date));
});
}
public closeDialog() {
this._dialogParams = undefined;
this._snapshots = undefined;
this._restoring = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || (!this._snapshots && !this._restoring)) {
return html``;
}
const snapshotCount = this._snapshots?.length || 0;
return html`
<ha-dialog
open
hideActions
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("dialog.addon_restore.title", {
name: this._dialogParams.addon.name,
})
)}
>
${this._restoring
? html`<div class="restore">
<ha-circular-progress size="large" active></ha-circular-progress>
<span
>${this._dialogParams.supervisor.localize(
"dialog.addon_restore.restore_in_progress"
)}
</span>
</div>`
: html`${this._dialogParams.supervisor.localize(
"dialog.addon_restore.description",
{
name: this._dialogParams.addon.name,
count: snapshotCount,
}
)}
${this._snapshots?.map(
(snapshot) =>
html`<ha-settings-row three-lines>
<span slot="heading">
${snapshot.name || snapshot.slug}
</span>
<span slot="description">
<div>
${this._dialogParams!.supervisor.localize(
"dialog.addon_restore.version",
{
version:
snapshot.addons.find(
(addon) =>
addon.slug === this._dialogParams?.addon.slug
)?.version ||
this._dialogParams!.supervisor.localize(
"dialog.addon_restore.no_version"
),
}
)}
</div>
${relativeTime(new Date(snapshot.date), this.hass.localize)}
</span>
<mwc-button
.snapshot=${snapshot}
@click=${this._restoreClicked}
>
${this._dialogParams!.supervisor.localize(
"dialog.addon_restore.restore"
)}
</mwc-button>
</ha-settings-row>`
)}`}
</ha-dialog>
`;
}
private async _restoreClicked(ev: CustomEvent) {
let password: string | null = null;
const snapshot: HassioSnapshotDetail = (ev.currentTarget as any).snapshot;
if (snapshot.protected) {
password = await showPromptDialog(this, {
text: this._dialogParams?.supervisor.localize(
"dialog.addon_restore.protected"
),
inputLabel: this._dialogParams?.supervisor.localize(
"dialog.addon_restore.password"
),
inputType: "password",
});
await nextRender();
if (!password) {
return;
}
}
this._restoring = true;
const data: HassioPartialSnapshotCreateParams = {
addons: [this._dialogParams!.addon.slug],
};
if (password) {
data.password = password;
}
try {
await supervisorRestorePartialSnapshot(this.hass, snapshot.slug, data);
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
await nextRender();
return;
}
this._dialogParams?.onRestore();
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
.restore {
display: flex;
flex-direction: column;
align-items: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-addon-restore": HassioAddonRestoreDialog;
}
}

View File

@ -0,0 +1,22 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { HassioSnapshot } from "../../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioAddonRestoreDialogParams {
supervisor: Supervisor;
snapshots: HassioSnapshot[];
addon: HassioAddonDetails;
onRestore: () => void;
}
export const showHassioAddonRestoreDialog = (
element: HTMLElement,
dialogParams: HassioAddonRestoreDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-addon-restore",
dialogImport: () => import("./dialog-hassio-addon-restore"),
dialogParams,
});
};

View File

@ -39,7 +39,7 @@ export interface HassioSnapshotDetail extends HassioSnapshot {
}
export interface HassioFullSnapshotCreateParams {
name: string;
name?: string;
password?: string;
}
export interface HassioPartialSnapshotCreateParams
@ -194,3 +194,26 @@ export const uploadSnapshot = async (
}
return resp.json();
};
export const supervisorRestorePartialSnapshot = async (
hass: HomeAssistant,
slug: string,
data: HassioPartialSnapshotCreateParams
) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: `/snapshots/${slug}/restore/partial`,
method: "post",
timeout: null,
data,
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/snapshots/${slug}/restore/partial`,
data
);
};

View File

@ -3630,6 +3630,7 @@
"restart": "restart",
"start": "start",
"stop": "stop",
"restore": "restore",
"install": "install",
"uninstall": "uninstall",
"rebuild": "rebuild",
@ -3978,6 +3979,16 @@
"create_snapshot": "Create a snapshot of {name} before updating",
"updating": "Updating {name} to version {version}",
"snapshotting": "Creating snapshot of {name}"
},
"addon_restore": {
"title": "Restore {name}",
"description": "You have {count, plural,\n one {one snapshot}\n other {{count} snapshots}\n} that includes restore points for {name}",
"version": "Version {version}",
"no_version": "No version",
"restore": "Restore",
"password": "Password",
"protected": "The snapshot is password protected, please provide the password for it.",
"restore_in_progress": "Restore in progress"
}
}
}