mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 08:29:52 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			20250903.3
			...
			restore-ad
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					495c35017e | ||
| 
						 | 
					31c66fed6e | ||
| 
						 | 
					c1df1d41f9 | 
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										190
									
								
								hassio/src/dialogs/addon/dialog-hassio-addon-restore.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								hassio/src/dialogs/addon/dialog-hassio-addon-restore.ts
									
									
									
									
									
										Normal 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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								hassio/src/dialogs/addon/show-dialog-hassio-addon-restore.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								hassio/src/dialogs/addon/show-dialog-hassio-addon-restore.ts
									
									
									
									
									
										Normal 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,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@@ -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
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -3630,6 +3630,7 @@
 | 
			
		||||
        "restart": "restart",
 | 
			
		||||
        "start": "start",
 | 
			
		||||
        "stop": "stop",
 | 
			
		||||
        "restore": "restore",
 | 
			
		||||
        "install": "install",
 | 
			
		||||
        "uninstall": "uninstall",
 | 
			
		||||
        "rebuild": "rebuild",
 | 
			
		||||
@@ -3981,6 +3982,16 @@
 | 
			
		||||
        "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",
 | 
			
		||||
        "enter_password": "[%key:supervisor::snapshot::enter_password%]",
 | 
			
		||||
        "protected": "The snapshot is password protected, please provide the password for it.",
 | 
			
		||||
        "restore_in_progress": "Restore in progress"
 | 
			
		||||
      },
 | 
			
		||||
      "hardware": {
 | 
			
		||||
        "title": "Hardware",
 | 
			
		||||
        "search": "Search hardware",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user