Add panel to Backup integration (#11671)

Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Joakim Sørensen 2022-03-30 19:22:15 +02:00 committed by GitHub
parent b5861869e3
commit 2b1457e1cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 306 additions and 0 deletions

36
src/data/backup.ts Normal file
View File

@ -0,0 +1,36 @@
import { HomeAssistant } from "../types";
export interface BackupContent {
slug: string;
date: string;
name: string;
size: number;
path: string;
}
export interface BackupData {
backing_up: boolean;
backups: BackupContent[];
}
export const getBackupDownloadUrl = (slug: string) =>
`/api/backup/download/${slug}`;
export const fetchBackupInfo = (hass: HomeAssistant): Promise<BackupData> =>
hass.callWS({
type: "backup/info",
});
export const removeBackup = (
hass: HomeAssistant,
slug: string
): Promise<void> =>
hass.callWS({
type: "backup/remove",
slug,
});
export const generateBackup = (hass: HomeAssistant): Promise<BackupContent> =>
hass.callWS({
type: "backup/generate",
});

View File

@ -0,0 +1,224 @@
import { mdiDelete, mdiDownload, mdiPlus } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoize from "memoize-one";
import { relativeTime } from "../../../common/datetime/relative_time";
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-circular-progress";
import "../../../components/ha-fab";
import "../../../components/ha-icon";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon";
import { getSignedPath } from "../../../data/auth";
import {
BackupContent,
BackupData,
fetchBackupInfo,
generateBackup,
getBackupDownloadUrl,
removeBackup,
} from "../../../data/backup";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import { configSections } from "../ha-panel-config";
@customElement("ha-config-backup")
class HaConfigBackup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide!: boolean;
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@state() private _backupData?: BackupData;
private _columns = memoize(
(narrow, _language): DataTableColumnContainer => ({
name: {
title: this.hass.localize("ui.panel.config.backup.name"),
sortable: true,
filterable: true,
grows: true,
template: (entry: string, backup: BackupContent) =>
html`${entry}
<div class="secondary">${backup.path}</div>`,
},
size: {
title: this.hass.localize("ui.panel.config.backup.size"),
width: "15%",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB",
},
date: {
title: this.hass.localize("ui.panel.config.backup.created"),
width: "15%",
direction: "desc",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.locale),
},
actions: {
title: "",
width: "15%",
template: (_: string, backup: BackupContent) =>
html`<ha-icon-overflow-menu
.hass=${this.hass}
.narrow=${this.narrow}
.items=${[
// Download Button
{
path: mdiDownload,
label: this.hass.localize(
"ui.panel.config.backup.download_backup"
),
action: () => this._downloadBackup(backup),
},
// Delete button
{
path: mdiDelete,
label: this.hass.localize(
"ui.panel.config.backup.remove_backup"
),
action: () => this._removeBackup(backup),
},
]}
style="color: var(--secondary-text-color)"
>
</ha-icon-overflow-menu>`,
},
})
);
private _getItems = memoize((backupItems: BackupContent[]) =>
backupItems.map((backup) => ({
name: backup.name,
slug: backup.slug,
date: backup.date,
size: backup.size,
path: backup.path,
}))
);
protected render(): TemplateResult {
if (!this.hass || this._backupData === undefined) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.backup}
.columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._getItems(this._backupData.backups)}
.noDataText=${this.hass.localize("ui.panel.config.backup.no_bakcups")}
>
<ha-fab
slot="fab"
?disabled=${this._backupData.backing_up}
.label=${this._backupData.backing_up
? this.hass.localize("ui.panel.config.backup.creating_backup")
: this.hass.localize("ui.panel.config.backup.create_backup")}
extended
@click=${this._generateBackup}
>
${this._backupData.backing_up
? html`<ha-circular-progress
slot="icon"
active
></ha-circular-progress>`
: html`<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>`}
</ha-fab>
</hass-tabs-subpage-data-table>
`;
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._getBackups();
}
private async _getBackups(): Promise<void> {
this._backupData = await fetchBackupInfo(this.hass);
}
private async _downloadBackup(backup: BackupContent): Promise<void> {
const signedUrl = await getSignedPath(
this.hass,
getBackupDownloadUrl(backup.slug)
);
fileDownload(signedUrl.path);
}
private async _generateBackup(): Promise<void> {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize("ui.panel.config.backup.create.title"),
text: this.hass.localize("ui.panel.config.backup.create.description"),
confirmText: this.hass.localize("ui.panel.config.backup.create.confirm"),
});
if (!confirm) {
return;
}
generateBackup(this.hass)
.then(() => this._getBackups())
.catch((err) => showAlertDialog(this, { text: (err as Error).message }));
await this._getBackups();
}
private async _removeBackup(backup: BackupContent): Promise<void> {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize("ui.panel.config.backup.remove.title"),
text: this.hass.localize("ui.panel.config.backup.remove.description", {
name: backup.name,
}),
confirmText: this.hass.localize("ui.panel.config.backup.remove.confirm"),
});
if (!confirm) {
return;
}
await removeBackup(this.hass, backup.slug);
await this._getBackups();
}
static get styles(): CSSResultGroup {
return [
css`
ha-fab[disabled] {
--mdc-theme-secondary: var(--disabled-text-color) !important;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-backup": HaConfigBackup;
}
}

View File

@ -1,5 +1,6 @@
import {
mdiAccount,
mdiBackupRestore,
mdiBadgeAccountHorizontal,
mdiCellphoneCog,
mdiCog,
@ -63,6 +64,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconColor: "#64B5F6",
component: "blueprint",
},
{
path: "/config/backup",
translationKey: "backup",
iconPath: mdiBackupRestore,
iconColor: "#4084CD",
component: "backup",
},
{
path: "/hassio",
translationKey: "supervisor",
@ -105,6 +113,15 @@ export const configSections: { [name: string]: PageNavigation[] } = {
core: true,
},
],
backup: [
{
path: "/config/backup",
translationKey: "ui.panel.config.backup.caption",
iconPath: mdiBackupRestore,
iconColor: "#4084CD",
component: "backup",
},
],
devices: [
{
component: "integrations",
@ -287,6 +304,10 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-automation",
load: () => import("./automation/ha-config-automation"),
},
backup: {
tag: "ha-config-backup",
load: () => import("./backup/ha-config-backup"),
},
blueprint: {
tag: "ha-config-blueprint",
load: () => import("./blueprint/ha-config-blueprint"),

View File

@ -1070,6 +1070,10 @@
"title": "Automations & Scenes",
"description": "Manage automations, scenes, scripts and helpers"
},
"backup": {
"title": "Backup",
"description": "Generate backups of your Home Assistant configuration"
},
"blueprints": {
"title": "Blueprints",
"description": "Pre-made automations and scripts by the community"
@ -1160,6 +1164,27 @@
"confirmation_text": "All devices in this area will become unassigned."
}
},
"backup": {
"caption": "[%key:ui::panel::config::dashboard::backup::title%]",
"create_backup": "[%key:supervisor::backup::create_backup%]",
"creating_backup": "Backup is currently being created",
"download_backup": "[%key:supervisor::backup::download_backup%]",
"remove_backup": "[%key:supervisor::backup::delete_backup_title%]",
"name": "[%key:supervisor::backup::name%]",
"size": "[%key:supervisor::backup::size%]",
"created": "[%key:supervisor::backup::created%]",
"no_backups": "[%key:supervisor::backup::no_backups%]",
"create": {
"title": "Create backup",
"description": "Create a backup of your current configuration directory, this will take some time.",
"confirm": "create"
},
"remove": {
"title": "Remove backup",
"description": "Are you sure you want to remove the backup with the name {name}?",
"confirm": "[%key:ui::common::remove%]"
}
},
"tag": {
"caption": "Tags",
"description": "Trigger automations when an NFC tag, QR code, etc. is scanned",