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,7 +12,9 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) => const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = (staticDir) => (...parts) => const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts); path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) { function copyTranslations(staticDir) {

View File

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

View File

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

View File

@ -87,7 +87,7 @@ class HassioCardContent extends LitElement {
color: var(--paper-green-400); color: var(--paper-green-400);
} }
ha-svg-icon.hassupdate, ha-svg-icon.hassupdate,
ha-svg-icon.snapshot { ha-svg-icon.backup {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }
ha-svg-icon.not_available { 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-file-upload";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
"snapshot-uploaded": { snapshot: HassioSnapshot }; "backup-uploaded": { backup: HassioBackup };
} }
} }
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-snapshot") @customElement("hassio-upload-backup")
export class HassioUploadSnapshot extends LitElement { export class HassioUploadBackup extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() public value: string | null = null; @state() public value: string | null = null;
@ -37,7 +34,7 @@ export class HassioUploadSnapshot extends LitElement {
.uploading=${this._uploading} .uploading=${this._uploading}
.icon=${mdiFolderUpload} .icon=${mdiFolderUpload}
accept="application/x-tar" accept="application/x-tar"
label="Upload snapshot" label="Upload backup"
@file-picked=${this._uploadFile} @file-picked=${this._uploadFile}
auto-open-file-dialog auto-open-file-dialog
></ha-file-upload> ></ha-file-upload>
@ -49,10 +46,10 @@ export class HassioUploadSnapshot extends LitElement {
if (file.size > MAX_FILE_SIZE) { if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Snapshot file is too big", title: "Backup file is too big",
text: html`The maximum allowed filesize is 1GB.<br /> text: html`The maximum allowed filesize is 1GB.<br />
<a <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" target="_blank"
>Have a look here on how to restore it.</a >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)) { if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Unsupported file format", title: "Unsupported file format",
text: "Please choose a Home Assistant snapshot file (.tar)", text: "Please choose a Home Assistant backup file (.tar)",
confirmText: "ok", confirmText: "ok",
}); });
return; return;
} }
this._uploading = true; this._uploading = true;
try { try {
const snapshot = await uploadSnapshot(this.hass, file); const backup = await uploadBackup(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data }); fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Upload failed", title: "Upload failed",
@ -87,6 +84,6 @@ export class HassioUploadSnapshot extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { 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 "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio"; import type { HaRadio } from "../../../src/components/ha-radio";
import { import {
HassioFullSnapshotCreateParams, HassioFullBackupCreateParams,
HassioPartialSnapshotCreateParams, HassioPartialBackupCreateParams,
HassioSnapshotDetail, HassioBackupDetail,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@ -64,17 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
})) }))
.sort((a, b) => (a.name > b.name ? 1 : -1)); .sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-snapshot-content") @customElement("supervisor-backup-content")
export class SupervisorSnapshotContent extends LitElement { export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc; @property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor; @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[]; @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 homeAssistant = false;
@property({ type: Boolean }) public snapshotHasPassword = false; @property({ type: Boolean }) public backupHasPassword = false;
@property({ type: Boolean }) public onboarding = 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) { public willUpdate(changedProps) {
super.willUpdate(changedProps); super.willUpdate(changedProps);
if (!this.hasUpdated) { if (!this.hasUpdated) {
this.folders = _computeFolders( this.folders = _computeFolders(
this.snapshot this.backup
? this.snapshot.folders ? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"] : ["homeassistant", "ssl", "share", "media", "addons/local"]
); );
this.addons = _computeAddons( this.addons = _computeAddons(
this.snapshot this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
? this.snapshot.addons
: this.supervisor?.supervisor.addons
); );
this.snapshotType = this.snapshot?.type || "full"; this.backupType = this.backup?.type || "full";
this.snapshotName = this.snapshot?.name || ""; this.backupName = this.backup?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false; this.backupHasPassword = this.backup?.protected || false;
} }
} }
private _localize = (string: string) => private _localize = (string: string) =>
this.supervisor?.localize(`snapshot.${string}`) || this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`); this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult { protected render(): TemplateResult {
@ -120,64 +118,64 @@ export class SupervisorSnapshotContent extends LitElement {
return html``; return html``;
} }
const foldersSection = const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined; this.backupType === "partial" ? this._getSection("folders") : undefined;
const addonsSection = const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined; this.backupType === "partial" ? this._getSection("addons") : undefined;
return html` return html`
${this.snapshot ${this.backup
? html`<div class="details"> ? html`<div class="details">
${this.snapshot.type === "full" ${this.backup.type === "full"
? this._localize("full_snapshot") ? this._localize("full_backup")
: this._localize("partial_snapshot")} : this._localize("partial_backup")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br /> (${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${this.hass ${this.hass
? formatDateTime(new Date(this.snapshot.date), this.hass.locale) ? formatDateTime(new Date(this.backup.date), this.hass.locale)
: this.snapshot.date} : this.backup.date}
</div>` </div>`
: html`<paper-input : html`<paper-input
name="snapshotName" name="backupName"
.label=${this.supervisor?.localize("snapshot.name") || "Name"} .label=${this._localize("name")}
.value=${this.snapshotName} .value=${this.backupName}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>`} </paper-input>`}
${!this.snapshot || this.snapshot.type === "full" ${!this.backup || this.backup.type === "full"
? html`<div class="sub-header"> ? html`<div class="sub-header">
${!this.snapshot ${!this.backup
? this._localize("type") ? this._localize("type")
: this._localize("select_type")} : this._localize("select_type")}
</div> </div>
<div class="snapshot-types"> <div class="backup-types">
<ha-formfield .label=${this._localize("full_snapshot")}> <ha-formfield .label=${this._localize("full_backup")}>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="full" value="full"
name="snapshotType" name="backupType"
.checked=${this.snapshotType === "full"} .checked=${this.backupType === "full"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
<ha-formfield .label=${this._localize("partial_snapshot")}> <ha-formfield .label=${this._localize("partial_backup")}>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="partial" value="partial"
name="snapshotType" name="backupType"
.checked=${this.snapshotType === "partial"} .checked=${this.backupType === "partial"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
</div>` </div>`
: ""} : ""}
${this.snapshotType === "partial" ${this.backupType === "partial"
? html`<div class="partial-picker"> ? html`<div class="partial-picker">
${this.snapshot && this.snapshot.homeassistant ${this.backup && this.backup.homeassistant
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
label="Home Assistant" label="Home Assistant"
.iconPath=${mdiHomeAssistant} .iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant} .version=${this.backup.homeassistant}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
> >
@ -233,38 +231,38 @@ export class SupervisorSnapshotContent extends LitElement {
: ""} : ""}
</div> ` </div> `
: ""} : ""}
${this.snapshotType === "partial" && ${this.backupType === "partial" &&
(!this.snapshot || this.snapshotHasPassword) (!this.backup || this.backupHasPassword)
? html`<hr />` ? html`<hr />`
: ""} : ""}
${!this.snapshot ${!this.backup
? html`<ha-formfield ? html`<ha-formfield
class="password" class="password"
.label=${this._localize("password_protection")} .label=${this._localize("password_protection")}
> >
<ha-checkbox <ha-checkbox
.checked=${this.snapshotHasPassword} .checked=${this.backupHasPassword}
@change=${this._toggleHasPassword} @change=${this._toggleHasPassword}
> >
</ha-checkbox> </ha-checkbox>
</ha-formfield>` </ha-formfield>`
: ""} : ""}
${this.snapshotHasPassword ${this.backupHasPassword
? html` ? html`
<paper-input <paper-input
.label=${this._localize("password")} .label=${this._localize("password")}
type="password" type="password"
name="snapshotPassword" name="backupPassword"
.value=${this.snapshotPassword} .value=${this.backupPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input> </paper-input>
${!this.snapshot ${!this.backup
? html` <paper-input ? html` <paper-input
.label=${this.supervisor?.localize("confirm_password")} .label=${this._localize("confirm_password")}
type="password" type="password"
name="confirmSnapshotPassword" name="confirmBackupPassword"
.value=${this.confirmSnapshotPassword} .value=${this.confirmBackupPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>` </paper-input>`
@ -307,7 +305,7 @@ export class SupervisorSnapshotContent extends LitElement {
display: block; display: block;
margin: 0 -14px -16px; margin: 0 -14px -16px;
} }
.snapshot-types { .backup-types {
display: flex; display: flex;
margin-left: -13px; margin-left: -13px;
} }
@ -317,23 +315,23 @@ export class SupervisorSnapshotContent extends LitElement {
`; `;
} }
public snapshotDetails(): public backupDetails():
| HassioPartialSnapshotCreateParams | HassioPartialBackupCreateParams
| HassioFullSnapshotCreateParams { | HassioFullBackupCreateParams {
const data: any = {}; const data: any = {};
if (!this.snapshot) { if (!this.backup) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale); data.name = this.backupName || formatDate(new Date(), this.hass.locale);
} }
if (this.snapshotHasPassword) { if (this.backupHasPassword) {
data.password = this.snapshotPassword; data.password = this.backupPassword;
if (!this.snapshot) { if (!this.backup) {
data.confirm_password = this.confirmSnapshotPassword; data.confirm_password = this.confirmBackupPassword;
} }
} }
if (this.snapshotType === "full") { if (this.backupType === "full") {
return data; return data;
} }
@ -415,7 +413,7 @@ export class SupervisorSnapshotContent extends LitElement {
} }
private _toggleHasPassword(): void { private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword; this.backupHasPassword = !this.backupHasPassword;
} }
private _toggleSection(ev): void { private _toggleSection(ev): void {
@ -445,6 +443,6 @@ export class SupervisorSnapshotContent extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent; "supervisor-backup-content": SupervisorBackupContent;
} }
} }

View File

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

View File

@ -6,20 +6,20 @@ import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-snapshot"; import "../../components/hassio-upload-backup";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"; import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
@customElement("dialog-hassio-snapshot-upload") @customElement("dialog-hassio-backup-upload")
export class DialogHassioSnapshotUpload export class DialogHassioBackupUpload
extends LitElement extends LitElement
implements HassDialog<HassioSnapshotUploadDialogParams> implements HassDialog<HassioBackupUploadDialogParams>
{ {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioSnapshotUploadDialogParams; @state() private _params?: HassioBackupUploadDialogParams;
public async showDialog( public async showDialog(
params: HassioSnapshotUploadDialogParams params: HassioBackupUploadDialogParams
): Promise<void> { ): Promise<void> {
this._params = params; this._params = params;
await this.updateComplete; await this.updateComplete;
@ -27,8 +27,8 @@ export class DialogHassioSnapshotUpload
public closeDialog(): void { public closeDialog(): void {
if (this._params && !this._params.onboarding) { if (this._params && !this._params.onboarding) {
if (this._params.reloadSnapshot) { if (this._params.reloadBackup) {
this._params.reloadSnapshot(); this._params.reloadBackup();
} }
} }
this._params = undefined; this._params = undefined;
@ -51,23 +51,23 @@ export class DialogHassioSnapshotUpload
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> Upload snapshot </span> <span slot="title"> Upload backup </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
<hassio-upload-snapshot <hassio-upload-backup
@snapshot-uploaded=${this._snapshotUploaded} @backup-uploaded=${this._backupUploaded}
.hass=${this.hass} .hass=${this.hass}
></hassio-upload-snapshot> ></hassio-upload-backup>
</ha-dialog> </ha-dialog>
`; `;
} }
private _snapshotUploaded(ev) { private _backupUploaded(ev) {
const snapshot = ev.detail.snapshot; const backup = ev.detail.backup;
this._params?.showSnapshot(snapshot.slug); this._params?.showBackup(backup.slug);
this.closeDialog(); this.closeDialog();
} }
@ -94,6 +94,6 @@ export class DialogHassioSnapshotUpload
declare global { declare global {
interface HTMLElementTagNameMap { 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 { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
fetchHassioSnapshotInfo, fetchHassioBackupInfo,
HassioSnapshotDetail, HassioBackupDetail,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/backup";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@ -23,44 +23,45 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download"; import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-snapshot-content"; import "../../components/supervisor-backup-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
@customElement("dialog-hassio-snapshot") @customElement("dialog-hassio-backup")
class HassioSnapshotDialog class HassioBackupDialog
extends LitElement extends LitElement
implements HassDialog<HassioSnapshotDialogParams> implements HassDialog<HassioBackupDialogParams>
{ {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string; @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") @query("supervisor-backup-content")
private _snapshotContent!: SupervisorSnapshotContent; private _backupContent!: SupervisorBackupContent;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioBackupDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
this._dialogParams = params; this._dialogParams = params;
this._restoringSnapshot = false; this._restoringBackup = false;
} }
public closeDialog() { public closeDialog() {
this._snapshot = undefined; this._backup = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._restoringSnapshot = false; this._restoringBackup = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) { if (!this._dialogParams || !this._backup) {
return html``; return html``;
} }
return html` return html`
@ -72,26 +73,26 @@ class HassioSnapshotDialog
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title">${this._snapshot.name}</span> <span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
${this._restoringSnapshot ${this._restoringBackup
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-backup-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot} .backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false} .onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize} .localize=${this._dialogParams.localize}
> >
</supervisor-snapshot-content>`} </supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button <mwc-button
.disabled=${this._restoringSnapshot} .disabled=${this._restoringBackup}
slot="secondaryAction" slot="secondaryAction"
@click=${this._restoreClicked} @click=${this._restoreClicked}
> >
@ -108,8 +109,8 @@ class HassioSnapshotDialog
<mwc-icon-button slot="trigger" alt="menu"> <mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item> <mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item> <mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu>` </ha-button-menu>`
: ""} : ""}
</ha-dialog> </ha-dialog>
@ -150,30 +151,30 @@ class HassioSnapshotDialog
} }
private async _restoreClicked() { private async _restoreClicked() {
const snapshotDetails = this._snapshotContent.snapshotDetails(); const backupDetails = this._backupContent.backupDetails();
this._restoringSnapshot = true; this._restoringBackup = true;
if (this._snapshotContent.snapshotType === "full") { if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(snapshotDetails); await this._fullRestoreClicked(backupDetails);
} else { } else {
await this._partialRestoreClicked(snapshotDetails); await this._partialRestoreClicked(backupDetails);
} }
this._restoringSnapshot = false; this._restoringBackup = false;
} }
private async _partialRestoreClicked(snapshotDetails) { private async _partialRestoreClicked(backupDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore backup",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?", title: "Are you sure you want partially to restore this backup?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@ -186,8 +187,12 @@ class HassioSnapshotDialog
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`, `hassio/${
snapshotDetails atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
) )
.then( .then(
() => { () => {
@ -199,29 +204,29 @@ class HassioSnapshotDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(backupDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
} }
private async _fullRestoreClicked(snapshotDetails) { private async _fullRestoreClicked(backupDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore backup",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: title:
"Are you sure you want to wipe your system and restore this snapshot?", "Are you sure you want to wipe your system and restore this backup?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@ -233,8 +238,12 @@ class HassioSnapshotDialog
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`, `hassio/${
snapshotDetails atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
) )
.then( .then(
() => { () => {
@ -246,9 +255,9 @@ class HassioSnapshotDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(backupDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
@ -257,7 +266,7 @@ class HassioSnapshotDialog
private async _deleteClicked() { private async _deleteClicked() {
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?", title: "Are you sure you want to delete this backup?",
confirmText: "delete", confirmText: "delete",
dismissText: "cancel", dismissText: "cancel",
})) }))
@ -267,7 +276,14 @@ class HassioSnapshotDialog
this.hass 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( .then(
() => { () => {
if (this._dialogParams!.onDelete) { if (this._dialogParams!.onDelete) {
@ -286,7 +302,11 @@ class HassioSnapshotDialog
try { try {
signedPath = await getSignedPath( signedPath = await getSignedPath(
this.hass, 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) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
@ -298,7 +318,7 @@ class HassioSnapshotDialog
if (window.location.href.includes("ui.nabu.casa")) { if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, { const confirm = await showConfirmationDialog(this, {
title: "Potential slow download", 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", confirmText: "continue",
dismissText: "cancel", dismissText: "cancel",
}); });
@ -310,19 +330,19 @@ class HassioSnapshotDialog
fileDownload( fileDownload(
this, this,
signedPath.path, signedPath.path,
`home_assistant_snapshot_${slugify(this._computeName)}.tar` `home_assistant_backup_${slugify(this._computeName)}.tar`
); );
} }
private get _computeName() { private get _computeName() {
return this._snapshot return this._backup
? this._snapshot.name || this._snapshot.slug ? this._backup.name || this._backup.slug
: "Unnamed snapshot"; : "Unnamed backup";
} }
} }
declare global { declare global {
interface HTMLElementTagNameMap { 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 { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
createHassioFullSnapshot, createHassioFullBackup,
createHassioPartialSnapshot, createHassioPartialBackup,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content"; import "../../components/supervisor-backup-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot"; import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
@customElement("dialog-hassio-create-snapshot") @customElement("dialog-hassio-create-backup")
class HassioCreateSnapshotDialog extends LitElement { class HassioCreateBackupDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams; @state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _error?: string; @state() private _error?: string;
@state() private _creatingSnapshot = false; @state() private _creatingBackup = false;
@query("supervisor-snapshot-content") @query("supervisor-backup-content")
private _snapshotContent!: SupervisorSnapshotContent; private _backupContent!: SupervisorBackupContent;
public showDialog(params: HassioCreateSnapshotDialogParams) { public showDialog(params: HassioCreateBackupDialogParams) {
this._dialogParams = params; this._dialogParams = params;
this._creatingSnapshot = false; this._creatingBackup = false;
} }
public closeDialog() { public closeDialog() {
this._dialogParams = undefined; this._dialogParams = undefined;
this._creatingSnapshot = false; this._creatingBackup = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@ -52,74 +52,74 @@ class HassioCreateSnapshotDialog extends LitElement {
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, 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` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-backup-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
> >
</supervisor-snapshot-content>`} </supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}> <mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")} ${this._dialogParams.supervisor.localize("common.close")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button
.disabled=${this._creatingSnapshot} .disabled=${this._creatingBackup}
slot="primaryAction" slot="primaryAction"
@click=${this._createSnapshot} @click=${this._createBackup}
> >
${this._dialogParams.supervisor.localize("snapshot.create")} ${this._dialogParams.supervisor.localize("backup.create")}
</mwc-button> </mwc-button>
</ha-dialog> </ha-dialog>
`; `;
} }
private async _createSnapshot(): Promise<void> { private async _createBackup(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") { if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, { showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize( title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create" "backup.could_not_create"
), ),
text: this._dialogParams!.supervisor.localize( text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running", "backup.create_blocked_not_running",
"state", "state",
this._dialogParams!.supervisor.info.state this._dialogParams!.supervisor.info.state
), ),
}); });
return; return;
} }
const snapshotDetails = this._snapshotContent.snapshotDetails(); const backupDetails = this._backupContent.backupDetails();
this._creatingSnapshot = true; this._creatingBackup = true;
this._error = ""; this._error = "";
if (snapshotDetails.password && !snapshotDetails.password.length) { if (backupDetails.password && !backupDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password" "backup.enter_password"
); );
this._creatingSnapshot = false; this._creatingBackup = false;
return; return;
} }
if ( if (
snapshotDetails.password && backupDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password backupDetails.password !== backupDetails.confirm_password
) { ) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"snapshot.passwords_not_matching" "backup.passwords_not_matching"
); );
this._creatingSnapshot = false; this._creatingBackup = false;
return; return;
} }
delete snapshotDetails.confirm_password; delete backupDetails.confirm_password;
try { try {
if (this._snapshotContent.snapshotType === "full") { if (this._backupContent.backupType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails); await createHassioFullBackup(this.hass, backupDetails);
} else { } else {
await createHassioPartialSnapshot(this.hass, snapshotDetails); await createHassioPartialBackup(this.hass, backupDetails);
} }
this._dialogParams!.onCreate(); this._dialogParams!.onCreate();
@ -127,7 +127,7 @@ class HassioCreateSnapshotDialog extends LitElement {
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._creatingSnapshot = false; this._creatingBackup = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@ -146,6 +146,6 @@ class HassioCreateSnapshotDialog extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { 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 { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams { export interface HassioBackupDialogParams {
slug: string; slug: string;
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
@ -10,13 +10,13 @@ export interface HassioSnapshotDialogParams {
localize?: LocalizeFunc; localize?: LocalizeFunc;
} }
export const showHassioSnapshotDialog = ( export const showHassioBackupDialog = (
element: HTMLElement, element: HTMLElement,
dialogParams: HassioSnapshotDialogParams dialogParams: HassioBackupDialogParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot", dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-snapshot"), dialogImport: () => import("./dialog-hassio-backup"),
dialogParams, dialogParams,
}); });
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -165,7 +165,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: "Home Assistant Core", name: "Home Assistant Core",
version: this.supervisor.core.version_latest, version: this.supervisor.core.version_latest,
snapshotParams: { backupParams: {
name: `core_${this.supervisor.core.version}`, name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"], folders: ["homeassistant"],
homeassistant: true, 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>` </onboarding-create-user>`
: ""} : ""}
${this._supervisor ${this._supervisor
? html`<onboarding-restore-snapshot ? html`<onboarding-restore-backup
.localize=${this.localize} .localize=${this.localize}
.restoring=${this._restoring} .restoring=${this._restoring}
.discoveryInformation=${this._discoveryInformation} .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; return this._steps ? this._steps.find((stp) => !stp.done) : undefined;
} }
private _restoringSnapshot() { private _restoringBackup() {
this._restoring = true; this._restoring = true;
} }
@ -183,12 +183,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
].includes(response.installation_type); ].includes(response.installation_type);
if (this._supervisor) { if (this._supervisor) {
// Only load if we have supervisor // Only load if we have supervisor
import("./onboarding-restore-snapshot"); import("./onboarding-restore-backup");
} }
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error( console.error(
"Something went wrong loading onboarding-restore-snapshot", "Something went wrong loading onboarding-restore-backup",
err err
); );
} }

View File

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

View File

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

View File

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