mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-16 22:06:34 +00:00
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:
parent
b5861869e3
commit
2b1457e1cd
36
src/data/backup.ts
Normal file
36
src/data/backup.ts
Normal 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",
|
||||||
|
});
|
224
src/panels/config/backup/ha-config-backup.ts
Normal file
224
src/panels/config/backup/ha-config-backup.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
mdiAccount,
|
mdiAccount,
|
||||||
|
mdiBackupRestore,
|
||||||
mdiBadgeAccountHorizontal,
|
mdiBadgeAccountHorizontal,
|
||||||
mdiCellphoneCog,
|
mdiCellphoneCog,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
@ -63,6 +64,13 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
iconColor: "#64B5F6",
|
iconColor: "#64B5F6",
|
||||||
component: "blueprint",
|
component: "blueprint",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/config/backup",
|
||||||
|
translationKey: "backup",
|
||||||
|
iconPath: mdiBackupRestore,
|
||||||
|
iconColor: "#4084CD",
|
||||||
|
component: "backup",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/hassio",
|
path: "/hassio",
|
||||||
translationKey: "supervisor",
|
translationKey: "supervisor",
|
||||||
@ -105,6 +113,15 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
|||||||
core: true,
|
core: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
backup: [
|
||||||
|
{
|
||||||
|
path: "/config/backup",
|
||||||
|
translationKey: "ui.panel.config.backup.caption",
|
||||||
|
iconPath: mdiBackupRestore,
|
||||||
|
iconColor: "#4084CD",
|
||||||
|
component: "backup",
|
||||||
|
},
|
||||||
|
],
|
||||||
devices: [
|
devices: [
|
||||||
{
|
{
|
||||||
component: "integrations",
|
component: "integrations",
|
||||||
@ -287,6 +304,10 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
tag: "ha-config-automation",
|
tag: "ha-config-automation",
|
||||||
load: () => import("./automation/ha-config-automation"),
|
load: () => import("./automation/ha-config-automation"),
|
||||||
},
|
},
|
||||||
|
backup: {
|
||||||
|
tag: "ha-config-backup",
|
||||||
|
load: () => import("./backup/ha-config-backup"),
|
||||||
|
},
|
||||||
blueprint: {
|
blueprint: {
|
||||||
tag: "ha-config-blueprint",
|
tag: "ha-config-blueprint",
|
||||||
load: () => import("./blueprint/ha-config-blueprint"),
|
load: () => import("./blueprint/ha-config-blueprint"),
|
||||||
|
@ -1070,6 +1070,10 @@
|
|||||||
"title": "Automations & Scenes",
|
"title": "Automations & Scenes",
|
||||||
"description": "Manage automations, scenes, scripts and helpers"
|
"description": "Manage automations, scenes, scripts and helpers"
|
||||||
},
|
},
|
||||||
|
"backup": {
|
||||||
|
"title": "Backup",
|
||||||
|
"description": "Generate backups of your Home Assistant configuration"
|
||||||
|
},
|
||||||
"blueprints": {
|
"blueprints": {
|
||||||
"title": "Blueprints",
|
"title": "Blueprints",
|
||||||
"description": "Pre-made automations and scripts by the community"
|
"description": "Pre-made automations and scripts by the community"
|
||||||
@ -1160,6 +1164,27 @@
|
|||||||
"confirmation_text": "All devices in this area will become unassigned."
|
"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": {
|
"tag": {
|
||||||
"caption": "Tags",
|
"caption": "Tags",
|
||||||
"description": "Trigger automations when an NFC tag, QR code, etc. is scanned",
|
"description": "Trigger automations when an NFC tag, QR code, etc. is scanned",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user