Rename snapshot -> backup (#9393)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Joakim Sørensen 2021-08-12 11:56:13 +02:00 committed by GitHub
parent a9850f9641
commit eff48acdf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 632 additions and 583 deletions

View File

@ -12,8 +12,10 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = (staticDir) => (...parts) =>
path.resolve(staticDir, ...parts);
const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) {
const staticPath = genStaticPath(staticDir);

View File

@ -987,7 +987,7 @@ class HassioAddonInfo extends LitElement {
supervisor: this.supervisor,
name: this.addon.name,
version: this.addon.version_latest,
snapshotParams: {
backupParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,

View File

@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioSnapshots,
fetchHassioBackups,
friendlyFolderName,
HassioSnapshot,
reloadHassioSnapshots,
removeSnapshot,
} from "../../../src/data/hassio/snapshot";
HassioBackup,
reloadHassioBackups,
removeBackup,
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@ -40,14 +40,14 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-snapshots")
export class HassioSnapshots extends LitElement {
@customElement("hassio-backups")
export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@ -58,9 +58,9 @@ export class HassioSnapshots extends LitElement {
@property({ type: Boolean }) public isWide!: boolean;
@state() private _selectedSnapshots: string[] = [];
@state() private _selectedBackups: string[] = [];
@state() private _snapshots?: HassioSnapshot[] = [];
@state() private _backups?: HassioBackup[] = [];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
@ -75,26 +75,26 @@ export class HassioSnapshots extends LitElement {
}
public async refreshData() {
await reloadHassioSnapshots(this.hass);
await this.fetchSnapshots();
await reloadHassioBackups(this.hass);
await this.fetchBackups();
}
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
private _computeBackupContent = (backup: HassioBackup): string => {
if (backup.type === "full") {
return this.supervisor.localize("backup.full_backup");
}
const content: string[] = [];
if (snapshot.content.homeassistant) {
if (backup.content.homeassistant) {
content.push("Home Assistant");
}
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
if (backup.content.folders.length !== 0) {
for (const folder of backup.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
@ -117,16 +117,16 @@ export class HassioSnapshots extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("snapshot.name") || "",
title: this.supervisor?.localize("backup.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
template: (entry: string, backup: any) =>
html`${entry || backup.slug}
<div class="secondary">${backup.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("snapshot.created") || "",
title: this.supervisor?.localize("backup.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
@ -143,10 +143,10 @@ export class HassioSnapshots extends LitElement {
})
);
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
private _backupData = memoizeOne((backups: HassioBackup[]) =>
backups.map((backup) => ({
...backup,
secondary: this._computeBackupContent(backup),
}))
);
@ -160,11 +160,11 @@ export class HassioSnapshots extends LitElement {
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._snapshotData(this._snapshots || [])}
.data=${this._backupData(this._backups || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
@ -187,12 +187,12 @@ export class HassioSnapshots extends LitElement {
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor?.localize("snapshot.upload_snapshot")}
${this.supervisor?.localize("backup.upload_backup")}
</mwc-list-item>`
: ""}
</ha-button-menu>
${this._selectedSnapshots.length
${this._selectedBackups.length
? html`<div
class=${classMap({
"header-toolbar": this.narrow,
@ -201,8 +201,8 @@ export class HassioSnapshots extends LitElement {
slot="header"
>
<p class="selected-txt">
${this.supervisor.localize("snapshot.selected", {
number: this._selectedSnapshots.length,
${this.supervisor.localize("backup.selected", {
number: this._selectedBackups.length,
})}
</p>
<div class="header-btns">
@ -212,7 +212,7 @@ export class HassioSnapshots extends LitElement {
@click=${this._deleteSelected}
class="warning"
>
${this.supervisor.localize("snapshot.delete_selected")}
${this.supervisor.localize("backup.delete_selected")}
</mwc-button>
`
: html`
@ -224,7 +224,7 @@ export class HassioSnapshots extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("snapshot.delete_selected")}
${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip>
`}
</div>
@ -233,8 +233,8 @@ export class HassioSnapshots extends LitElement {
<ha-fab
slot="fab"
@click=${this._createSnapshot}
.label=${this.supervisor.localize("snapshot.create_snapshot")}
@click=${this._createBackup}
.label=${this.supervisor.localize("backup.create_backup")}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@ -249,7 +249,7 @@ export class HassioSnapshots extends LitElement {
this.refreshData();
break;
case 1:
this._showUploadSnapshotDialog();
this._showUploadBackupDialog();
break;
}
}
@ -257,33 +257,33 @@ export class HassioSnapshots extends LitElement {
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedSnapshots = ev.detail.value;
this._selectedBackups = ev.detail.value;
}
private _showUploadSnapshotDialog() {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) =>
showHassioSnapshotDialog(this, {
private _showUploadBackupDialog() {
showBackupUploadDialog(this, {
showBackup: (slug: string) =>
showHassioBackupDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
onDelete: () => this.fetchBackups(),
}),
reloadSnapshot: () => this.refreshData(),
reloadBackup: () => this.refreshData(),
});
}
private async fetchSnapshots() {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
private async fetchBackups() {
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
}
private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
number: this._selectedSnapshots.length,
title: this.supervisor.localize("backup.delete_backup_title"),
text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedBackups.length,
}),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
});
if (!confirm) {
@ -292,44 +292,44 @@ export class HassioSnapshots extends LitElement {
try {
await Promise.all(
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("snapshot.failed_to_delete"),
title: this.supervisor.localize("backup.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioSnapshotDialog(this, {
showHassioBackupDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
onDelete: () => this.fetchBackups(),
});
}
private _createSnapshot() {
private _createBackup() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"),
title: this.supervisor!.localize("backup.could_not_create"),
text: this.supervisor!.localize(
"snapshot.create_blocked_not_running",
"backup.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateSnapshotDialog(this, {
showHassioCreateBackupDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(),
onCreate: () => this.fetchBackups(),
});
}
@ -378,6 +378,6 @@ export class HassioSnapshots extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-snapshots": HassioSnapshots;
"hassio-backups": HassioBackups;
}
}

View File

@ -87,7 +87,7 @@ class HassioCardContent extends LitElement {
color: var(--paper-green-400);
}
ha-svg-icon.hassupdate,
ha-svg-icon.snapshot {
ha-svg-icon.backup {
color: var(--paper-item-icon-color);
}
ha-svg-icon.not_available {

View File

@ -8,23 +8,20 @@ import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
declare global {
interface HASSDomEvents {
"snapshot-uploaded": { snapshot: HassioSnapshot };
"backup-uploaded": { backup: HassioBackup };
}
}
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-snapshot")
export class HassioUploadSnapshot extends LitElement {
@customElement("hassio-upload-backup")
export class HassioUploadBackup extends LitElement {
public hass!: HomeAssistant;
@state() public value: string | null = null;
@ -37,7 +34,7 @@ export class HassioUploadSnapshot extends LitElement {
.uploading=${this._uploading}
.icon=${mdiFolderUpload}
accept="application/x-tar"
label="Upload snapshot"
label="Upload backup"
@file-picked=${this._uploadFile}
auto-open-file-dialog
></ha-file-upload>
@ -49,10 +46,10 @@ export class HassioUploadSnapshot extends LitElement {
if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, {
title: "Snapshot file is too big",
title: "Backup file is too big",
text: html`The maximum allowed filesize is 1GB.<br />
<a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install"
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
target="_blank"
>Have a look here on how to restore it.</a
>`,
@ -64,15 +61,15 @@ export class HassioUploadSnapshot extends LitElement {
if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, {
title: "Unsupported file format",
text: "Please choose a Home Assistant snapshot file (.tar)",
text: "Please choose a Home Assistant backup file (.tar)",
confirmText: "ok",
});
return;
}
this._uploading = true;
try {
const snapshot = await uploadSnapshot(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data });
const backup = await uploadBackup(this.hass, file);
fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err) {
showAlertDialog(this, {
title: "Upload failed",
@ -87,6 +84,6 @@ export class HassioUploadSnapshot extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-upload-snapshot": HassioUploadSnapshot;
"hassio-upload-backup": HassioUploadBackup;
}
}

View File

@ -11,10 +11,10 @@ import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
HassioFullSnapshotCreateParams,
HassioPartialSnapshotCreateParams,
HassioSnapshotDetail,
} from "../../../src/data/hassio/snapshot";
HassioFullBackupCreateParams,
HassioPartialBackupCreateParams,
HassioBackupDetail,
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types";
@ -64,17 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-snapshot-content")
export class SupervisorSnapshotContent extends LitElement {
@customElement("supervisor-backup-content")
export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
@property({ attribute: false }) public backup?: HassioBackupDetail;
@property() public snapshotType: HassioSnapshotDetail["type"] = "full";
@property() public backupType: HassioBackupDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[];
@ -82,37 +82,35 @@ export class SupervisorSnapshotContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public snapshotHasPassword = false;
@property({ type: Boolean }) public backupHasPassword = false;
@property({ type: Boolean }) public onboarding = false;
@property() public snapshotName = "";
@property() public backupName = "";
@property() public snapshotPassword = "";
@property() public backupPassword = "";
@property() public confirmSnapshotPassword = "";
@property() public confirmBackupPassword = "";
public willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this.folders = _computeFolders(
this.snapshot
? this.snapshot.folders
this.backup
? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.snapshot
? this.snapshot.addons
: this.supervisor?.supervisor.addons
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
);
this.snapshotType = this.snapshot?.type || "full";
this.snapshotName = this.snapshot?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false;
this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || "";
this.backupHasPassword = this.backup?.protected || false;
}
}
private _localize = (string: string) =>
this.supervisor?.localize(`snapshot.${string}`) ||
this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
@ -120,64 +118,64 @@ export class SupervisorSnapshotContent extends LitElement {
return html``;
}
const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
this.backupType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
this.backupType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.snapshot
${this.backup
? html`<div class="details">
${this.snapshot.type === "full"
? this._localize("full_snapshot")
: this._localize("partial_snapshot")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${this.backup.type === "full"
? this._localize("full_backup")
: this._localize("partial_backup")}
(${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${this.hass
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
: this.snapshot.date}
? formatDateTime(new Date(this.backup.date), this.hass.locale)
: this.backup.date}
</div>`
: html`<paper-input
name="snapshotName"
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
.value=${this.snapshotName}
name="backupName"
.label=${this._localize("name")}
.value=${this.backupName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.snapshot || this.snapshot.type === "full"
${!this.backup || this.backup.type === "full"
? html`<div class="sub-header">
${!this.snapshot
${!this.backup
? this._localize("type")
: this._localize("select_type")}
</div>
<div class="snapshot-types">
<ha-formfield .label=${this._localize("full_snapshot")}>
<div class="backup-types">
<ha-formfield .label=${this._localize("full_backup")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="snapshotType"
.checked=${this.snapshotType === "full"}
name="backupType"
.checked=${this.backupType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield .label=${this._localize("partial_snapshot")}>
<ha-formfield .label=${this._localize("partial_backup")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="snapshotType"
.checked=${this.snapshotType === "partial"}
name="backupType"
.checked=${this.backupType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${this.snapshotType === "partial"
${this.backupType === "partial"
? html`<div class="partial-picker">
${this.snapshot && this.snapshot.homeassistant
${this.backup && this.backup.homeassistant
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant}
.version=${this.backup.homeassistant}
>
</supervisor-formfield-label>`}
>
@ -233,38 +231,38 @@ export class SupervisorSnapshotContent extends LitElement {
: ""}
</div> `
: ""}
${this.snapshotType === "partial" &&
(!this.snapshot || this.snapshotHasPassword)
${this.backupType === "partial" &&
(!this.backup || this.backupHasPassword)
? html`<hr />`
: ""}
${!this.snapshot
${!this.backup
? html`<ha-formfield
class="password"
.label=${this._localize("password_protection")}
>
<ha-checkbox
.checked=${this.snapshotHasPassword}
.checked=${this.backupHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${this.snapshotHasPassword
${this.backupHasPassword
? html`
<paper-input
.label=${this._localize("password")}
type="password"
name="snapshotPassword"
.value=${this.snapshotPassword}
name="backupPassword"
.value=${this.backupPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
${!this.snapshot
${!this.backup
? html` <paper-input
.label=${this.supervisor?.localize("confirm_password")}
.label=${this._localize("confirm_password")}
type="password"
name="confirmSnapshotPassword"
.value=${this.confirmSnapshotPassword}
name="confirmBackupPassword"
.value=${this.confirmBackupPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
@ -307,7 +305,7 @@ export class SupervisorSnapshotContent extends LitElement {
display: block;
margin: 0 -14px -16px;
}
.snapshot-types {
.backup-types {
display: flex;
margin-left: -13px;
}
@ -317,23 +315,23 @@ export class SupervisorSnapshotContent extends LitElement {
`;
}
public snapshotDetails():
| HassioPartialSnapshotCreateParams
| HassioFullSnapshotCreateParams {
public backupDetails():
| HassioPartialBackupCreateParams
| HassioFullBackupCreateParams {
const data: any = {};
if (!this.snapshot) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
if (!this.backup) {
data.name = this.backupName || formatDate(new Date(), this.hass.locale);
}
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
if (!this.snapshot) {
data.confirm_password = this.confirmSnapshotPassword;
if (this.backupHasPassword) {
data.password = this.backupPassword;
if (!this.backup) {
data.confirm_password = this.confirmBackupPassword;
}
}
if (this.snapshotType === "full") {
if (this.backupType === "full") {
return data;
}
@ -415,7 +413,7 @@ export class SupervisorSnapshotContent extends LitElement {
}
private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword;
this.backupHasPassword = !this.backupHasPassword;
}
private _toggleSection(ev): void {
@ -445,6 +443,6 @@ export class SupervisorSnapshotContent extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent;
"supervisor-backup-content": SupervisorBackupContent;
}
}

View File

@ -162,7 +162,7 @@ export class HassioUpdate extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
snapshotParams: {
backupParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,

View File

@ -6,20 +6,20 @@ 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";
import "../../components/hassio-upload-snapshot";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
import "../../components/hassio-upload-backup";
import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
@customElement("dialog-hassio-snapshot-upload")
export class DialogHassioSnapshotUpload
@customElement("dialog-hassio-backup-upload")
export class DialogHassioBackupUpload
extends LitElement
implements HassDialog<HassioSnapshotUploadDialogParams>
implements HassDialog<HassioBackupUploadDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioSnapshotUploadDialogParams;
@state() private _params?: HassioBackupUploadDialogParams;
public async showDialog(
params: HassioSnapshotUploadDialogParams
params: HassioBackupUploadDialogParams
): Promise<void> {
this._params = params;
await this.updateComplete;
@ -27,8 +27,8 @@ export class DialogHassioSnapshotUpload
public closeDialog(): void {
if (this._params && !this._params.onboarding) {
if (this._params.reloadSnapshot) {
this._params.reloadSnapshot();
if (this._params.reloadBackup) {
this._params.reloadBackup();
}
}
this._params = undefined;
@ -51,23 +51,23 @@ export class DialogHassioSnapshotUpload
>
<div slot="heading">
<ha-header-bar>
<span slot="title"> Upload snapshot </span>
<span slot="title"> Upload backup </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}
<hassio-upload-backup
@backup-uploaded=${this._backupUploaded}
.hass=${this.hass}
></hassio-upload-snapshot>
></hassio-upload-backup>
</ha-dialog>
`;
}
private _snapshotUploaded(ev) {
const snapshot = ev.detail.snapshot;
this._params?.showSnapshot(snapshot.slug);
private _backupUploaded(ev) {
const backup = ev.detail.backup;
this._params?.showBackup(backup.slug);
this.closeDialog();
}
@ -94,6 +94,6 @@ export class DialogHassioSnapshotUpload
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload;
"dialog-hassio-backup-upload": DialogHassioBackupUpload;
}
}

View File

@ -12,9 +12,9 @@ import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
fetchHassioBackupInfo,
HassioBackupDetail,
} from "../../../../src/data/hassio/backup";
import {
showAlertDialog,
showConfirmationDialog,
@ -23,44 +23,45 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog
@customElement("dialog-hassio-backup")
class HassioBackupDialog
extends LitElement
implements HassDialog<HassioSnapshotDialogParams>
implements HassDialog<HassioBackupDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _snapshot?: HassioSnapshotDetail;
@state() private _backup?: HassioBackupDetail;
@state() private _dialogParams?: HassioSnapshotDialogParams;
@state() private _dialogParams?: HassioBackupDialogParams;
@state() private _restoringSnapshot = false;
@state() private _restoringBackup = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
public async showDialog(params: HassioBackupDialogParams) {
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
this._dialogParams = params;
this._restoringSnapshot = false;
this._restoringBackup = false;
}
public closeDialog() {
this._snapshot = undefined;
this._backup = undefined;
this._dialogParams = undefined;
this._restoringSnapshot = false;
this._restoringBackup = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) {
if (!this._dialogParams || !this._backup) {
return html``;
}
return html`
@ -72,26 +73,26 @@ class HassioSnapshotDialog
>
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._snapshot.name}</span>
<span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
${this._restoringSnapshot
${this._restoringBackup
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
>
</supervisor-snapshot-content>`}
</supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button
.disabled=${this._restoringSnapshot}
.disabled=${this._restoringBackup}
slot="secondaryAction"
@click=${this._restoreClicked}
>
@ -108,8 +109,8 @@ class HassioSnapshotDialog
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
<mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu>`
: ""}
</ha-dialog>
@ -150,30 +151,30 @@ class HassioSnapshotDialog
}
private async _restoreClicked() {
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._restoringSnapshot = true;
if (this._snapshotContent.snapshotType === "full") {
await this._fullRestoreClicked(snapshotDetails);
const backupDetails = this._backupContent.backupDetails();
this._restoringBackup = true;
if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(backupDetails);
} else {
await this._partialRestoreClicked(snapshotDetails);
await this._partialRestoreClicked(backupDetails);
}
this._restoringSnapshot = false;
this._restoringBackup = false;
}
private async _partialRestoreClicked(snapshotDetails) {
private async _partialRestoreClicked(backupDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?",
title: "Are you sure you want partially to restore this backup?",
confirmText: "restore",
dismissText: "cancel",
}))
@ -186,8 +187,12 @@ class HassioSnapshotDialog
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
snapshotDetails
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
)
.then(
() => {
@ -199,29 +204,29 @@ class HassioSnapshotDialog
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(snapshotDetails),
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(snapshotDetails) {
private async _fullRestoreClicked(backupDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
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 backup?",
confirmText: "restore",
dismissText: "cancel",
}))
@ -233,8 +238,12 @@ class HassioSnapshotDialog
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
snapshotDetails
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
)
.then(
() => {
@ -246,9 +255,9 @@ class HassioSnapshotDialog
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(snapshotDetails),
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
@ -257,7 +266,7 @@ class HassioSnapshotDialog
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?",
title: "Are you sure you want to delete this backup?",
confirmText: "delete",
dismissText: "cancel",
}))
@ -267,7 +276,14 @@ class HassioSnapshotDialog
this.hass
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
.callApi(
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/remove`
)
.then(
() => {
if (this._dialogParams!.onDelete) {
@ -286,7 +302,11 @@ class HassioSnapshotDialog
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
`/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
);
} catch (err) {
await showAlertDialog(this, {
@ -298,7 +318,7 @@ class HassioSnapshotDialog
if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, {
title: "Potential slow download",
text: "Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
text: "Downloading backups over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
confirmText: "continue",
dismissText: "cancel",
});
@ -310,19 +330,19 @@ class HassioSnapshotDialog
fileDownload(
this,
signedPath.path,
`home_assistant_snapshot_${slugify(this._computeName)}.tar`
`home_assistant_backup_${slugify(this._computeName)}.tar`
);
}
private get _computeName() {
return this._snapshot
? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot";
return this._backup
? this._backup.name || this._backup.slug
: "Unnamed backup";
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-snapshot": HassioSnapshotDialog;
"dialog-hassio-backup": HassioBackupDialog;
}
}

View File

@ -6,37 +6,37 @@ import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
} from "../../../../src/data/hassio/snapshot";
createHassioFullBackup,
createHassioPartialBackup,
} from "../../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@customElement("dialog-hassio-create-backup")
class HassioCreateBackupDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _error?: string;
@state() private _creatingSnapshot = false;
@state() private _creatingBackup = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
public showDialog(params: HassioCreateSnapshotDialogParams) {
public showDialog(params: HassioCreateBackupDialogParams) {
this._dialogParams = params;
this._creatingSnapshot = false;
this._creatingBackup = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingSnapshot = false;
this._creatingBackup = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@ -52,74 +52,74 @@ class HassioCreateSnapshotDialog extends LitElement {
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
this._dialogParams.supervisor.localize("backup.create_backup")
)}
>
${this._creatingSnapshot
${this._creatingBackup
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</supervisor-snapshot-content>`}
</supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")}
</mwc-button>
<mwc-button
.disabled=${this._creatingSnapshot}
.disabled=${this._creatingBackup}
slot="primaryAction"
@click=${this._createSnapshot}
@click=${this._createBackup}
>
${this._dialogParams.supervisor.localize("snapshot.create")}
${this._dialogParams.supervisor.localize("backup.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createSnapshot(): Promise<void> {
private async _createBackup(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create"
"backup.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running",
"backup.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingSnapshot = true;
const backupDetails = this._backupContent.backupDetails();
this._creatingBackup = true;
this._error = "";
if (snapshotDetails.password && !snapshotDetails.password.length) {
if (backupDetails.password && !backupDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password"
"backup.enter_password"
);
this._creatingSnapshot = false;
this._creatingBackup = false;
return;
}
if (
snapshotDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password
backupDetails.password &&
backupDetails.password !== backupDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.passwords_not_matching"
"backup.passwords_not_matching"
);
this._creatingSnapshot = false;
this._creatingBackup = false;
return;
}
delete snapshotDetails.confirm_password;
delete backupDetails.confirm_password;
try {
if (this._snapshotContent.snapshotType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails);
if (this._backupContent.backupType === "full") {
await createHassioFullBackup(this.hass, backupDetails);
} else {
await createHassioPartialSnapshot(this.hass, snapshotDetails);
await createHassioPartialBackup(this.hass, backupDetails);
}
this._dialogParams!.onCreate();
@ -127,7 +127,7 @@ class HassioCreateSnapshotDialog extends LitElement {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingSnapshot = false;
this._creatingBackup = false;
}
static get styles(): CSSResultGroup {
@ -146,6 +146,6 @@ class HassioCreateSnapshotDialog extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
"dialog-hassio-create-backup": HassioCreateBackupDialog;
}
}

View File

@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-backup-upload";
export interface HassioBackupUploadDialogParams {
showBackup: (slug: string) => void;
reloadBackup?: () => Promise<void>;
onboarding?: boolean;
}
export const showBackupUploadDialog = (
element: HTMLElement,
dialogParams: HassioBackupUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup-upload",
dialogImport: () => import("./dialog-hassio-backup-upload"),
dialogParams,
});
};

View File

@ -2,7 +2,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams {
export interface HassioBackupDialogParams {
slug: string;
onDelete?: () => void;
onboarding?: boolean;
@ -10,13 +10,13 @@ export interface HassioSnapshotDialogParams {
localize?: LocalizeFunc;
}
export const showHassioSnapshotDialog = (
export const showHassioBackupDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotDialogParams
dialogParams: HassioBackupDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot",
dialogImport: () => import("./dialog-hassio-snapshot"),
dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-backup"),
dialogParams,
});
};

View File

@ -1,18 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioCreateSnapshotDialogParams {
export interface HassioCreateBackupDialogParams {
supervisor: Supervisor;
onCreate: () => void;
}
export const showHassioCreateSnapshotDialog = (
export const showHassioCreateBackupDialog = (
element: HTMLElement,
dialogParams: HassioCreateSnapshotDialogParams
dialogParams: HassioCreateBackupDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-create-snapshot",
dialogImport: () => import("./dialog-hassio-create-snapshot"),
dialogTag: "dialog-hassio-create-backup",
dialogImport: () => import("./dialog-hassio-create-backup"),
dialogParams,
});
};

View File

@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-snapshot-upload";
export interface HassioSnapshotUploadDialogParams {
showSnapshot: (slug: string) => void;
reloadSnapshot?: () => Promise<void>;
onboarding?: boolean;
}
export const showSnapshotUploadDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot-upload",
dialogImport: () => import("./dialog-hassio-snapshot-upload"),
dialogParams,
});
};

View File

@ -11,7 +11,7 @@ import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@ -22,9 +22,9 @@ class DialogSupervisorUpdate extends LitElement {
@state() private _opened = false;
@state() private _createSnapshot = true;
@state() private _createBackup = true;
@state() private _action: "snapshot" | "update" | null = null;
@state() private _action: "backup" | "update" | null = null;
@state() private _error?: string;
@ -41,7 +41,7 @@ class DialogSupervisorUpdate extends LitElement {
public closeDialog(): void {
this._action = null;
this._createSnapshot = true;
this._createBackup = true;
this._error = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@ -84,20 +84,20 @@ class DialogSupervisorUpdate extends LitElement {
<ha-settings-row>
<span slot="heading">
${this._dialogParams.supervisor.localize(
"dialog.update.snapshot"
"dialog.update.backup"
)}
</span>
<span slot="description">
${this._dialogParams.supervisor.localize(
"dialog.update.create_snapshot",
"dialog.update.create_backup",
"name",
this._dialogParams.name
)}
</span>
<ha-switch
.checked=${this._createSnapshot}
.checked=${this._createBackup}
haptic
@click=${this._toggleSnapshot}
@click=${this._toggleBackup}
>
</ha-switch>
</ha-settings-row>
@ -123,7 +123,7 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version
)
: this._dialogParams.supervisor.localize(
"dialog.update.snapshotting",
"dialog.update.creating_backup",
"name",
this._dialogParams.name
)}
@ -133,17 +133,17 @@ class DialogSupervisorUpdate extends LitElement {
`;
}
private _toggleSnapshot() {
this._createSnapshot = !this._createSnapshot;
private _toggleBackup() {
this._createBackup = !this._createBackup;
}
private async _update() {
if (this._createSnapshot) {
this._action = "snapshot";
if (this._createBackup) {
this._action = "backup";
try {
await createHassioPartialSnapshot(
await createHassioPartialBackup(
this.hass,
this._dialogParams!.snapshotParams
this._dialogParams!.backupParams
);
} catch (err) {
this._error = extractApiErrorMessage(err);

View File

@ -5,7 +5,7 @@ export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor;
name: string;
version: string;
snapshotParams: any;
backupParams: any;
updateHandler: () => Promise<void>;
}

View File

@ -26,7 +26,10 @@ const REDIRECTS: Redirects = {
redirect: "/hassio/system",
},
supervisor_snapshots: {
redirect: "/hassio/snapshots",
redirect: "/hassio/backups",
},
supervisor_backups: {
redirect: "/hassio/backups",
},
supervisor_store: {
redirect: "/hassio/store",

View File

@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./snapshots/hassio-snapshots";
import "./backups/hassio-backups";
import "./system/hassio-system";
@customElement("hassio-panel-router")
@ -23,6 +23,8 @@ class HassioPanelRouter extends HassRouterPage {
@property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
routes: {
dashboard: {
tag: "hassio-dashboard",
@ -30,8 +32,8 @@ class HassioPanelRouter extends HassRouterPage {
store: {
tag: "hassio-addon-store",
},
snapshots: {
tag: "hassio-snapshots",
backups: {
tag: "hassio-backups",
},
system: {
tag: "hassio-system",

View File

@ -23,6 +23,8 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
initialLoad: () => this._redirectIngress(),
showLoading: true,
routes: {
@ -30,7 +32,7 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-panel",
cache: true,
},
snapshots: "dashboard",
backups: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {

View File

@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
iconPath: mdiStore,
},
{
translationKey: "panel.snapshots",
path: `/hassio/snapshots`,
translationKey: "panel.backups",
path: `/hassio/backups`,
iconPath: mdiBackupRestore,
},
{

View File

@ -165,7 +165,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
snapshotParams: {
backupParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,

233
src/data/hassio/backup.ts Normal file
View File

@ -0,0 +1,233 @@
import { atLeastVersion } from "../../common/config/version";
import { HomeAssistant } from "../../types";
import { hassioApiResultExtractor, HassioResponse } from "./common";
export const friendlyFolderName = {
ssl: "SSL",
homeassistant: "Configuration",
"addons/local": "Local add-ons",
media: "Media",
share: "Share",
};
interface BackupContent {
homeassistant: boolean;
folders: string[];
addons: string[];
}
export interface HassioBackup {
slug: string;
date: string;
name: string;
type: "full" | "partial";
protected: boolean;
content: BackupContent;
}
export interface HassioBackupDetail extends HassioBackup {
size: number;
homeassistant: string;
addons: Array<{
slug: "ADDON_SLUG";
name: "NAME";
version: "INSTALLED_VERSION";
size: "SIZE_IN_MB";
}>;
repositories: string[];
folders: string[];
}
export interface HassioFullBackupCreateParams {
name: string;
password?: string;
confirm_password?: string;
}
export interface HassioPartialBackupCreateParams
extends HassioFullBackupCreateParams {
folders?: string[];
addons?: string[];
homeassistant?: boolean;
}
export const fetchHassioBackups = async (
hass: HomeAssistant
): Promise<HassioBackup[]> => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
const data: {
[key: string]: HassioBackup[];
} = await hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}`,
method: "get",
});
return data[
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
];
}
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<{ snapshots: HassioBackup[] }>>(
"GET",
`hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}`
)
).snapshots;
};
export const fetchHassioBackupInfo = async (
hass: HomeAssistant,
backup: string
): Promise<HassioBackupDetail> => {
if (hass) {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/${backup}/info`,
method: "get",
});
}
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<HassioBackupDetail>>(
"GET",
`hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/${backup}/info`
)
);
}
// When called from onboarding we don't have hass
const resp = await fetch(`/api/hassio/backups/${backup}/info`, {
method: "GET",
});
const data = (await resp.json()).data;
return data;
};
export const reloadHassioBackups = async (hass: HomeAssistant) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/reload`,
method: "post",
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/reload`
);
};
export const createHassioFullBackup = async (
hass: HomeAssistant,
data: HassioFullBackupCreateParams
) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/new/full`,
method: "post",
timeout: null,
data,
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/new/full`,
data
);
};
export const removeBackup = async (hass: HomeAssistant, slug: string) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/${slug}/remove`,
method: "post",
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/${slug}/remove`
);
};
export const createHassioPartialBackup = async (
hass: HomeAssistant,
data: HassioPartialBackupCreateParams
) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: `/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/new/partial`,
method: "post",
timeout: null,
data,
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/new/partial`,
data
);
};
export const uploadBackup = async (
hass: HomeAssistant,
file: File
): Promise<HassioResponse<HassioBackup>> => {
const fd = new FormData();
let resp;
fd.append("file", file);
if (hass) {
resp = await hass.fetchWithAuth(
`/api/hassio/${
atLeastVersion(hass.config.version, 2021, 9) ? "backups" : "snapshots"
}/new/upload`,
{
method: "POST",
body: fd,
}
);
} else {
// When called from onboarding we don't have hass
resp = await fetch("/api/hassio/backups/new/upload", {
method: "POST",
body: fd,
});
}
if (resp.status === 413) {
throw new Error("Uploaded backup is too large");
} else if (resp.status !== 200) {
throw new Error(`${resp.status} ${resp.statusText}`);
}
return resp.json();
};

View File

@ -1,197 +0,0 @@
import { atLeastVersion } from "../../common/config/version";
import { HomeAssistant } from "../../types";
import { hassioApiResultExtractor, HassioResponse } from "./common";
export const friendlyFolderName = {
ssl: "SSL",
homeassistant: "Configuration",
"addons/local": "Local add-ons",
media: "Media",
share: "Share",
};
interface SnapshotContent {
homeassistant: boolean;
folders: string[];
addons: string[];
}
export interface HassioSnapshot {
slug: string;
date: string;
name: string;
type: "full" | "partial";
protected: boolean;
content: SnapshotContent;
}
export interface HassioSnapshotDetail extends HassioSnapshot {
size: number;
homeassistant: string;
addons: Array<{
slug: "ADDON_SLUG";
name: "NAME";
version: "INSTALLED_VERSION";
size: "SIZE_IN_MB";
}>;
repositories: string[];
folders: string[];
}
export interface HassioFullSnapshotCreateParams {
name: string;
password?: string;
confirm_password?: string;
}
export interface HassioPartialSnapshotCreateParams
extends HassioFullSnapshotCreateParams {
folders?: string[];
addons?: string[];
homeassistant?: boolean;
}
export const fetchHassioSnapshots = async (
hass: HomeAssistant
): Promise<HassioSnapshot[]> => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
const data: { snapshots: HassioSnapshot[] } = await hass.callWS({
type: "supervisor/api",
endpoint: `/snapshots`,
method: "get",
});
return data.snapshots;
}
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<{ snapshots: HassioSnapshot[] }>>(
"GET",
"hassio/snapshots"
)
).snapshots;
};
export const fetchHassioSnapshotInfo = async (
hass: HomeAssistant,
snapshot: string
): Promise<HassioSnapshotDetail> => {
if (hass) {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
return hass.callWS({
type: "supervisor/api",
endpoint: `/snapshots/${snapshot}/info`,
method: "get",
});
}
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) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: "/snapshots/reload",
method: "post",
});
return;
}
await hass.callApi<HassioResponse<void>>("POST", `hassio/snapshots/reload`);
};
export const createHassioFullSnapshot = async (
hass: HomeAssistant,
data: HassioFullSnapshotCreateParams
) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: "/snapshots/new/full",
method: "post",
timeout: null,
data,
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/snapshots/new/full`,
data
);
};
export const removeSnapshot = async (hass: HomeAssistant, slug: string) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: `/snapshots/${slug}/remove`,
method: "post",
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/snapshots/${slug}/remove`
);
};
export const createHassioPartialSnapshot = async (
hass: HomeAssistant,
data: HassioPartialSnapshotCreateParams
) => {
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
await hass.callWS({
type: "supervisor/api",
endpoint: "/snapshots/new/partial",
method: "post",
timeout: null,
data,
});
return;
}
await hass.callApi<HassioResponse<void>>(
"POST",
`hassio/snapshots/new/partial`,
data
);
};
export const uploadSnapshot = async (
hass: HomeAssistant,
file: File
): Promise<HassioResponse<HassioSnapshot>> => {
const fd = new FormData();
let resp;
fd.append("file", file);
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(`${resp.status} ${resp.statusText}`);
}
return resp.json();
};

View File

@ -89,13 +89,13 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
</onboarding-create-user>`
: ""}
${this._supervisor
? html`<onboarding-restore-snapshot
? html`<onboarding-restore-backup
.localize=${this.localize}
.restoring=${this._restoring}
.discoveryInformation=${this._discoveryInformation}
@restoring=${this._restoringSnapshot}
@restoring=${this._restoringBackup}
>
</onboarding-restore-snapshot>`
</onboarding-restore-backup>`
: ""}
`;
}
@ -170,7 +170,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
return this._steps ? this._steps.find((stp) => !stp.done) : undefined;
}
private _restoringSnapshot() {
private _restoringBackup() {
this._restoring = true;
}
@ -183,12 +183,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
].includes(response.installation_type);
if (this._supervisor) {
// Only load if we have supervisor
import("./onboarding-restore-snapshot");
import("./onboarding-restore-backup");
}
} catch (err) {
// eslint-disable-next-line no-console
console.error(
"Something went wrong loading onboarding-restore-snapshot",
"Something went wrong loading onboarding-restore-backup",
err
);
}

View File

@ -2,8 +2,8 @@ import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
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 { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dialog-hassio-backup";
import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-card";
import {
@ -21,8 +21,8 @@ declare global {
}
}
@customElement("onboarding-restore-snapshot")
class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
@customElement("onboarding-restore-backup")
class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) {
@property() public localize!: LocalizeFunc;
@property() public language!: string;
@ -42,15 +42,15 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
<onboarding-loading></onboarding-loading>
</ha-card>`
: html`
<button class="link" @click=${this._uploadSnapshot}>
<button class="link" @click=${this._uploadBackup}>
${this.localize("ui.panel.page-onboarding.restore.description")}
</button>
`;
}
private _uploadSnapshot(): void {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) => this._showSnapshotDialog(slug),
private _uploadBackup(): void {
showBackupUploadDialog(this, {
showBackup: (slug: string) => this._showBackupDialog(slug),
onboarding: true,
});
}
@ -79,8 +79,8 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
}
}
private _showSnapshotDialog(slug: string): void {
showHassioSnapshotDialog(this, {
private _showBackupDialog(slug: string): void {
showHassioBackupDialog(this, {
slug,
onboarding: true,
localize: this.localize,
@ -118,6 +118,6 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
declare global {
interface HTMLElementTagNameMap {
"onboarding-restore-snapshot": OnboardingRestoreSnapshot;
"onboarding-restore-backup": OnboardingRestoreBackup;
}
}

View File

@ -127,8 +127,8 @@ export class ZwaveMigration extends LitElement {
</ol>
<p>
<b>
Please take a backup or a snapshot of your
environment before proceeding.
Please take a backup of your environment before
proceeding.
</b>
</p>
</div>

View File

@ -3700,19 +3700,19 @@
"finish": "Next"
},
"restore": {
"description": "Alternatively you can restore from a previous snapshot.",
"description": "Alternatively you can restore from a previous backup.",
"in_progress": "Restore in progress",
"show_log": "Show full log",
"hide_log": "Hide full log",
"full_snapshot": "[%key:supervisor::snapshot::full_snapshot%]",
"partial_snapshot": "[%key:supervisor::snapshot::partial_snapshot%]",
"type": "[%key:supervisor::snapshot::type%]",
"select_type": "[%key:supervisor::snapshot::select_type%]",
"folders": "[%key:supervisor::snapshot::folders%]",
"addons": "[%key:supervisor::snapshot::addons%]",
"password_protection": "[%key:supervisor::snapshot::password_protection%]",
"password": "[%key:supervisor::snapshot::password%]",
"confirm_password": "[%key:supervisor::snapshot::confirm_password%]"
"full_backup": "[%key:supervisor::backup::full_backup%]",
"partial_backup": "[%key:supervisor::backup::partial_backup%]",
"type": "[%key:supervisor::backup::type%]",
"select_type": "[%key:supervisor::backup::select_type%]",
"folders": "[%key:supervisor::backup::folders%]",
"addons": "[%key:supervisor::backup::addons%]",
"password_protection": "[%key:supervisor::backup::password_protection%]",
"password": "[%key:supervisor::backup::password%]",
"confirm_password": "[%key:supervisor::backup::confirm_password%]"
}
},
"custom": {
@ -3959,7 +3959,7 @@
},
"panel": {
"dashboard": "Dashboard",
"snapshots": "Snapshots",
"backups": "Backups",
"store": "Add-on Store",
"system": "System"
},
@ -4054,43 +4054,32 @@
"ram_usage": "Core RAM Usage"
}
},
"snapshot": {
"description": "Snapshots allow you to easily backup and restore all data of your Home Assistant instance.",
"available_snapshots": "Available Snapshots",
"no_snapshots": "You don't have any snapshots yet.",
"create_blocked_not_running": "Creating a snapshot is not possible right now because the system is in {state} state.",
"delete_selected": "Delete selected snapshots",
"delete_snapshot_title": "Delete snapshot",
"delete_snapshot_text": "Do you want to delete {number} {number, plural,\n one {snapshot}\n other {snapshots}\n}?",
"delete_snapshot_confirm": "delete",
"backup": {
"no_backups": "You don't have any backups yet.",
"create_blocked_not_running": "Creating a backup is not possible right now because the system is in {state} state.",
"delete_selected": "Delete selected backups",
"delete_backup_title": "Delete backup",
"delete_backup_text": "Do you want to delete {number} {number, plural,\n one {backup}\n other {backups}\n}?",
"delete_backup_confirm": "delete",
"selected": "{number} selected",
"failed_to_delete": "Failed to delete",
"could_not_create": "Could not create snapshot",
"upload_snapshot": "Upload snapshot",
"create_snapshot": "Create snapshot",
"could_not_create": "Could not create backup",
"upload_backup": "Upload backup",
"create_backup": "Create backup",
"create": "Create",
"created": "Created",
"name": "Snapshot name",
"type": "Snapshot type",
"name": "Backup name",
"type": "Backup type",
"select_type": "Select what to restore",
"security": "Security",
"full_snapshot": "Full snapshot",
"partial_snapshot": "Partial snapshot",
"full_backup": "Full backup",
"partial_backup": "Partial backup",
"addons": "Add-ons",
"folders": "Folders",
"password": "Snapshot password",
"confirm_password": "Confirm Snapshot password",
"password": "Backup password",
"confirm_password": "Confirm backup password",
"password_protection": "Password protection",
"password_protected": "password protected",
"enter_password": "Please enter a password.",
"passwords_not_matching": "The passwords does not match",
"folder": {
"homeassistant": "Home Assistant configuration",
"ssl": "SSL",
"share": "Share",
"media": "Media",
"addons/local": "Local add-ons"
}
"passwords_not_matching": "The passwords does not match"
},
"dialog": {
"network": {
@ -4133,10 +4122,10 @@
"text": "Do you want to restart the add-on with your changes?"
},
"update": {
"snapshot": "Snapshot",
"create_snapshot": "Create a snapshot of {name} before updating",
"backup": "Backup",
"create_backup": "Create a backup of {name} before updating",
"updating": "Updating {name} to version {version}",
"snapshotting": "Creating snapshot of {name}"
"creating_backup": "Creating backup of {name}"
},
"hardware": {
"title": "Hardware",