mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-10 01:47:46 +00:00
Early pushout of changes to the backup panel (#22321)
* Eary pushout of changes to the backup panel * Add location icons * Path is optional * Set backupSlug from route * No need for subscription mixin * update * Reorder * init details
This commit is contained in:
parent
2218a7121b
commit
0c2e62ec91
@ -1,11 +1,24 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface BackupContent {
|
||||
interface BackupSyncAgent {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface BaseBackupContent {
|
||||
slug: string;
|
||||
date: string;
|
||||
name: string;
|
||||
size: number;
|
||||
path: string;
|
||||
agents?: string[];
|
||||
}
|
||||
|
||||
export interface BackupContent extends BaseBackupContent {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export interface BackupSyncedContent extends BaseBackupContent {
|
||||
id: string;
|
||||
agent_id: string;
|
||||
}
|
||||
|
||||
export interface BackupData {
|
||||
@ -13,6 +26,11 @@ export interface BackupData {
|
||||
backups: BackupContent[];
|
||||
}
|
||||
|
||||
export interface BackupAgentsInfo {
|
||||
agents: BackupSyncAgent[];
|
||||
syncing: boolean;
|
||||
}
|
||||
|
||||
export const getBackupDownloadUrl = (slug: string) =>
|
||||
`/api/backup/download/${slug}`;
|
||||
|
||||
@ -21,6 +39,29 @@ export const fetchBackupInfo = (hass: HomeAssistant): Promise<BackupData> =>
|
||||
type: "backup/info",
|
||||
});
|
||||
|
||||
export const fetchBackupDetails = (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<{ backup: BackupContent }> =>
|
||||
hass.callWS({
|
||||
type: "backup/details",
|
||||
slug,
|
||||
});
|
||||
|
||||
export const fetchBackupAgentsInfo = (
|
||||
hass: HomeAssistant
|
||||
): Promise<BackupAgentsInfo> =>
|
||||
hass.callWS({
|
||||
type: "backup/agents/info",
|
||||
});
|
||||
|
||||
export const fetchBackupAgentsSynced = (
|
||||
hass: HomeAssistant
|
||||
): Promise<BackupSyncedContent[]> =>
|
||||
hass.callWS({
|
||||
type: "backup/agents/synced",
|
||||
});
|
||||
|
||||
export const removeBackup = (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
|
175
src/panels/config/backup/ha-config-backup-dashboard.ts
Normal file
175
src/panels/config/backup/ha-config-backup-dashboard.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../layouts/hass-subpage";
|
||||
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { fetchBackupAgentsInfo } from "../../../data/backup";
|
||||
|
||||
@customElement("ha-config-backup-dashboard")
|
||||
class HaConfigBackupDashboard extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _agents: { id: string }[] = [];
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchAgents();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/system"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.backup.caption")}
|
||||
>
|
||||
<div class="content">
|
||||
<div class="backup-status">
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="backup-status-contents">
|
||||
<div class="status-icon">
|
||||
<ha-icon icon="mdi:check-circle"></ha-icon>
|
||||
</div>
|
||||
<span>
|
||||
<div class="status-header">Backed up</div>
|
||||
<div class="status-text">
|
||||
Your configuration has been backed up.
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<ha-button
|
||||
@click=${this._showBackupList}
|
||||
class="show-all-backups"
|
||||
>
|
||||
Show all backups
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
<div class="backup-agents">
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="status-header">Locations</div>
|
||||
<div class="status-text">
|
||||
To keep your data safe it is recommended your backups is at
|
||||
least on two different locations and one of them is off-site.
|
||||
</div>
|
||||
${this._agents.length > 0
|
||||
? html`<mwc-list>
|
||||
${this._agents.map((agent) => {
|
||||
const [domain, name] = agent.id.split(".");
|
||||
return html` <ha-list-item
|
||||
graphic="medium"
|
||||
hasMeta
|
||||
.agent=${agent.id}
|
||||
@click=${this._showAgentSyncs}
|
||||
>
|
||||
<img
|
||||
.src=${brandsUrl({
|
||||
domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
alt="cloud"
|
||||
slot="graphic"
|
||||
/>
|
||||
<span>
|
||||
${this.hass.localize(`component.${domain}.title`) ||
|
||||
domain}:
|
||||
${name}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>`;
|
||||
})}
|
||||
</mwc-list>`
|
||||
: html`<p>No sync agents configured</p>`}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchAgents() {
|
||||
const resp = await fetchBackupAgentsInfo(this.hass);
|
||||
this._agents = resp.agents;
|
||||
}
|
||||
|
||||
private _showBackupList(): void {
|
||||
navigate("/config/backup/list");
|
||||
}
|
||||
|
||||
private _showAgentSyncs(event: Event): void {
|
||||
const agent = (event.currentTarget as any).agent;
|
||||
navigate(`/config/backup/list?agent=${agent}`);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
margin: 0 auto;
|
||||
gap: 24px;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.backup-status .card-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.backup-status-contents {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
color: var(--success-color);
|
||||
height: 100%;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.status-icon ha-icon {
|
||||
--mdc-icon-size: 40px;
|
||||
padding: 8px 16px 8px 8px;
|
||||
}
|
||||
|
||||
.status-header {
|
||||
font-size: 22px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-button.show-all-backups {
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-backup-dashboard": HaConfigBackupDashboard;
|
||||
}
|
||||
}
|
126
src/panels/config/backup/ha-config-backup-details.ts
Normal file
126
src/panels/config/backup/ha-config-backup-details.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../layouts/hass-subpage";
|
||||
|
||||
import "../../../components/ha-relative-time";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-settings-row";
|
||||
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
BackupAgentsInfo,
|
||||
BackupContent,
|
||||
fetchBackupDetails,
|
||||
} from "../../../data/backup";
|
||||
|
||||
@customElement("ha-config-backup-details")
|
||||
class HaConfigBackupDetails extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public backupAgentsInfo?: BackupAgentsInfo;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "backup-slug" }) public backupSlug!: string;
|
||||
|
||||
@state() private _backup?: BackupContent | null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
if (this.backupSlug) {
|
||||
this._fetchBackup();
|
||||
} else {
|
||||
this._error = "Backup slug not defined";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html`:(`;
|
||||
}
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup/list"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this._backup?.name || "Backup"}
|
||||
>
|
||||
<div class="content">
|
||||
${this._error &&
|
||||
html`<ha-alert alert-type="error">${this._error}</ha-alert>`}
|
||||
${this._backup === null
|
||||
? html`<ha-alert alert-type="warning" title="Not found">
|
||||
Backup matching ${this.backupSlug} not found
|
||||
</ha-alert>`
|
||||
: !this._backup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: html`
|
||||
<ha-card header="Backup">
|
||||
<div class="card-content">
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${this._backup.type || "partial"}
|
||||
</span>
|
||||
<span slot="description">Type</span>
|
||||
</ha-settings-row>
|
||||
${this._backup.homeassistant?.version &&
|
||||
html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${this._backup.homeassistant.version}
|
||||
</span>
|
||||
<span slot="description">Home Assistant Version</span>
|
||||
</ha-settings-row>`}
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${Math.ceil(this._backup.size * 10) / 10 + " MB"}
|
||||
</span>
|
||||
<span slot="description">Size</span>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${this._backup.date}
|
||||
slot="heading"
|
||||
capitalize
|
||||
>
|
||||
</ha-relative-time>
|
||||
<span slot="description">Created</span>
|
||||
</ha-settings-row>
|
||||
</div>
|
||||
</ha-card>
|
||||
`}
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchBackup() {
|
||||
try {
|
||||
const response = await fetchBackupDetails(this.hass, this.backupSlug);
|
||||
this._backup = response.backup;
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Could not fetch backup details";
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
margin: 0 auto;
|
||||
gap: 24px;
|
||||
display: grid;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-backup-details": HaConfigBackupDetails;
|
||||
}
|
||||
}
|
247
src/panels/config/backup/ha-config-backup-list.ts
Normal file
247
src/panels/config/backup/ha-config-backup-list.ts
Normal file
@ -0,0 +1,247 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoize from "memoize-one";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
BackupContent,
|
||||
BackupData,
|
||||
fetchBackupAgentsSynced,
|
||||
fetchBackupInfo,
|
||||
generateBackup,
|
||||
} 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 { brandsUrl } from "../../../util/brands-url";
|
||||
|
||||
const localAgent = "backup.local";
|
||||
|
||||
@customElement("ha-config-backup-list")
|
||||
class HaConfigBackup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _backupData?: BackupData;
|
||||
|
||||
private _columns = memoize(
|
||||
(
|
||||
narrow,
|
||||
_language,
|
||||
localize: LocalizeFunc
|
||||
): DataTableColumnContainer<BackupContent> => ({
|
||||
name: {
|
||||
title: localize("ui.panel.config.backup.name"),
|
||||
main: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
flex: 2,
|
||||
template: (backup) =>
|
||||
narrow || !backup.path
|
||||
? backup.name
|
||||
: html`${backup.name}
|
||||
<div class="secondary">${backup.path}</div>`,
|
||||
},
|
||||
path: {
|
||||
title: localize("ui.panel.config.backup.path"),
|
||||
hidden: !narrow,
|
||||
template: (backup) => backup.path || "-",
|
||||
},
|
||||
size: {
|
||||
title: localize("ui.panel.config.backup.size"),
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
|
||||
},
|
||||
date: {
|
||||
title: localize("ui.panel.config.backup.created"),
|
||||
direction: "desc",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (backup) =>
|
||||
relativeTime(new Date(backup.date), this.hass.locale),
|
||||
},
|
||||
locations: {
|
||||
title: "Locations",
|
||||
template: (backup) =>
|
||||
html`${[
|
||||
...(backup.path ? [localAgent] : []),
|
||||
...(backup.agents || []).sort(),
|
||||
].map((agent) => {
|
||||
const [domain, name] = agent.split(".");
|
||||
return html`<img
|
||||
title=${name}
|
||||
.src=${brandsUrl({
|
||||
domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
height="24"
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
alt=${name}
|
||||
slot="graphic"
|
||||
/>`;
|
||||
})}`,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
private _getItems = memoize((backupItems: BackupContent[]) =>
|
||||
backupItems.map((backup) => ({
|
||||
name: backup.name,
|
||||
slug: backup.slug,
|
||||
date: backup.date,
|
||||
size: backup.size,
|
||||
path: backup.path,
|
||||
agents: backup.agents,
|
||||
}))
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || this._backupData === undefined) {
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
hasFab
|
||||
.tabs=${[
|
||||
{
|
||||
translationKey: "ui.panel.config.backup.caption",
|
||||
path: `/config/backup/list`,
|
||||
},
|
||||
]}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config/backup/dashboard"
|
||||
clickable
|
||||
id="slug"
|
||||
.route=${this.route}
|
||||
@row-click=${this._showBackupDetails}
|
||||
.columns=${this._columns(
|
||||
this.narrow,
|
||||
this.hass.language,
|
||||
this.hass.localize
|
||||
)}
|
||||
.data=${this._getItems(this._backupData.backups)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.panel.config.backup.picker.search"
|
||||
)}
|
||||
>
|
||||
<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"
|
||||
indeterminate
|
||||
></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> {
|
||||
const backupData: Record<string, BackupContent> = {};
|
||||
const [local, synced] = await Promise.all([
|
||||
fetchBackupInfo(this.hass),
|
||||
fetchBackupAgentsSynced(this.hass),
|
||||
]);
|
||||
|
||||
for (const backup of local.backups) {
|
||||
backupData[backup.slug] = backup;
|
||||
}
|
||||
for (const agent of synced) {
|
||||
if (!(agent.slug in backupData)) {
|
||||
backupData[agent.slug] = { ...agent, agents: [agent.agent_id] };
|
||||
} else if (!("agents" in backupData[agent.slug])) {
|
||||
backupData[agent.slug].agents = [agent.agent_id];
|
||||
} else {
|
||||
backupData[agent.slug].agents!.push(agent.agent_id);
|
||||
}
|
||||
}
|
||||
this._backupData = {
|
||||
backing_up: local.backing_up,
|
||||
backups: Object.values(backupData),
|
||||
};
|
||||
}
|
||||
|
||||
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 _showBackupDetails(ev: CustomEvent): void {
|
||||
const slug = (ev.detail as RowClickedEvent).id;
|
||||
navigate(`/config/backup/details/${slug}`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
ha-fab[disabled] {
|
||||
--mdc-theme-secondary: var(--disabled-text-color) !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-backup-list": HaConfigBackup;
|
||||
}
|
||||
}
|
@ -1,242 +1,53 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import { mdiDelete, mdiDownload, mdiPlus } from "@mdi/js";
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
} 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 { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../layouts/hass-router-page";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./ha-config-backup-dashboard";
|
||||
|
||||
@customElement("ha-config-backup")
|
||||
class HaConfigBackup extends LitElement {
|
||||
class HaConfigBackup extends HassRouterPage {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
@property({ type: Boolean }) public isWide = false;
|
||||
|
||||
@state() private _backupData?: BackupData;
|
||||
@property({ type: Boolean }) public showAdvanced = false;
|
||||
|
||||
private _columns = memoize(
|
||||
(
|
||||
narrow,
|
||||
_language,
|
||||
localize: LocalizeFunc
|
||||
): DataTableColumnContainer<BackupContent> => ({
|
||||
name: {
|
||||
title: localize("ui.panel.config.backup.name"),
|
||||
main: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
flex: 2,
|
||||
template: narrow
|
||||
? undefined
|
||||
: (backup) =>
|
||||
html`${backup.name}
|
||||
<div class="secondary">${backup.path}</div>`,
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "dashboard",
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "ha-config-backup-dashboard",
|
||||
cache: true,
|
||||
},
|
||||
path: {
|
||||
title: localize("ui.panel.config.backup.path"),
|
||||
hidden: !narrow,
|
||||
list: {
|
||||
tag: "ha-config-backup-list",
|
||||
load: () => import("./ha-config-backup-list"),
|
||||
},
|
||||
size: {
|
||||
title: localize("ui.panel.config.backup.size"),
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
|
||||
},
|
||||
date: {
|
||||
title: localize("ui.panel.config.backup.created"),
|
||||
direction: "desc",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (backup) =>
|
||||
relativeTime(new Date(backup.date), this.hass.locale),
|
||||
details: {
|
||||
tag: "ha-config-backup-details",
|
||||
load: () => import("./ha-config-backup-details"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
actions: {
|
||||
title: "",
|
||||
type: "overflow-menu",
|
||||
showNarrow: true,
|
||||
hideable: false,
|
||||
moveable: false,
|
||||
template: (backup) =>
|
||||
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>`,
|
||||
},
|
||||
})
|
||||
);
|
||||
protected updatePageEl(pageEl, changedProps: PropertyValues) {
|
||||
pageEl.hass = this.hass;
|
||||
pageEl.narrow = this.narrow;
|
||||
pageEl.isWide = this.isWide;
|
||||
pageEl.route = this.routeTail;
|
||||
pageEl.showAdvanced = this.showAdvanced;
|
||||
|
||||
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>`;
|
||||
if (
|
||||
(!changedProps || changedProps.has("route")) &&
|
||||
this._currentPage === "details"
|
||||
) {
|
||||
pageEl.backupSlug = this.routeTail.path.substr(1);
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
hasFab
|
||||
.tabs=${[
|
||||
{
|
||||
translationKey: "ui.panel.config.backup.caption",
|
||||
path: `/config/backup`,
|
||||
},
|
||||
]}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config/system"
|
||||
.route=${this.route}
|
||||
.columns=${this._columns(
|
||||
this.narrow,
|
||||
this.hass.language,
|
||||
this.hass.localize
|
||||
)}
|
||||
.data=${this._getItems(this._backupData.backups)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.panel.config.backup.picker.search"
|
||||
)}
|
||||
>
|
||||
<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"
|
||||
indeterminate
|
||||
></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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user