mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-16 05:46:35 +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 {
|
||||
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"),
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user