mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 21:06:34 +00:00
Add overview summary for backups (#23343)
This commit is contained in:
parent
b693fd1edc
commit
92b02e39c9
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
mdiAlertCircleCheckOutline,
|
mdiAlertCircleOutline,
|
||||||
mdiAlertOutline,
|
mdiAlertOutline,
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
@ -16,7 +16,7 @@ type SummaryStatus = "success" | "error" | "info" | "warning" | "loading";
|
|||||||
|
|
||||||
const ICONS: Record<SummaryStatus, string> = {
|
const ICONS: Record<SummaryStatus, string> = {
|
||||||
success: mdiCheck,
|
success: mdiCheck,
|
||||||
error: mdiAlertCircleCheckOutline,
|
error: mdiAlertCircleOutline,
|
||||||
warning: mdiAlertOutline,
|
warning: mdiAlertOutline,
|
||||||
info: mdiInformationOutline,
|
info: mdiInformationOutline,
|
||||||
loading: mdiSync,
|
loading: mdiSync,
|
||||||
@ -60,6 +60,9 @@ class HaBackupSummaryCard extends LitElement {
|
|||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -71,7 +74,7 @@ class HaBackupSummaryCard extends LitElement {
|
|||||||
column-gap: 16px;
|
column-gap: 16px;
|
||||||
row-gap: 8px;
|
row-gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiCalendarSync, mdiGestureTap } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -62,6 +63,7 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item type="link" href="/config/backup/backups">
|
<ha-md-list-item type="link" href="/config/backup/backups">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||||
<div slot="headline">
|
<div slot="headline">
|
||||||
${automaticStats.count} automatic backups
|
${automaticStats.count} automatic backups
|
||||||
</div>
|
</div>
|
||||||
@ -71,6 +73,7 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
<ha-icon-next slot="end"></ha-icon-next>
|
<ha-icon-next slot="end"></ha-icon-next>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item type="link" href="/config/backup/backups">
|
<ha-md-list-item type="link" href="/config/backup/backups">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||||
<div slot="headline">${manualStats.count} manual backups</div>
|
<div slot="headline">${manualStats.count} manual backups</div>
|
||||||
<div slot="supporting-text">
|
<div slot="supporting-text">
|
||||||
${bytesToString(manualStats.size, 1)} in total
|
${bytesToString(manualStats.size, 1)} in total
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
import { mdiInformationOutline } from "@mdi/js";
|
||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-button";
|
||||||
|
import "../../../../../components/ha-card";
|
||||||
|
import "../../../../../components/ha-svg-icon";
|
||||||
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"button-click": undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-backup-overview-onboarding")
|
||||||
|
class HaBackupOverviewBackups extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
private async _setup() {
|
||||||
|
fireEvent(this, "button-click");
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="icon">
|
||||||
|
<ha-svg-icon .path=${mdiInformationOutline}></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
Set up automatic backups
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<p>
|
||||||
|
Backups are essential to a reliable smart home. They protect your
|
||||||
|
setup against failures and allows you to quickly have a working
|
||||||
|
system again. It is recommended to create a daily backup and keep
|
||||||
|
copies of the last 3 days on two different locations. And one of
|
||||||
|
them is off-site.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-button @click=${this._setup}>
|
||||||
|
Set up automatic backups
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 20px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.icon::before {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
.icon ha-svg-icon {
|
||||||
|
color: var(--primary-color);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-backup-overview-onboarding": HaBackupOverviewBackups;
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,15 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { ManagerStateEvent } from "../../../../data/backup_manager";
|
import type { ManagerStateEvent } from "../../../../../data/backup_manager";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "./ha-backup-summary-card";
|
import "../ha-backup-summary-card";
|
||||||
|
|
||||||
@customElement("ha-backup-summary-progress")
|
@customElement("ha-backup-overview-progress")
|
||||||
export class HaBackupSummaryProgress extends LitElement {
|
export class HaBackupOverviewProgress extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "has-action" })
|
|
||||||
public hasAction = false;
|
|
||||||
|
|
||||||
private get _heading() {
|
private get _heading() {
|
||||||
switch (this.manager.manager_state) {
|
switch (this.manager.manager_state) {
|
||||||
case "create_backup":
|
case "create_backup":
|
||||||
@ -93,9 +90,7 @@ export class HaBackupSummaryProgress extends LitElement {
|
|||||||
.heading=${this._heading}
|
.heading=${this._heading}
|
||||||
.description=${this._description}
|
.description=${this._description}
|
||||||
status="loading"
|
status="loading"
|
||||||
.hasAction=${this.hasAction}
|
|
||||||
>
|
>
|
||||||
<slot name="action" slot="action"></slot>
|
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -103,6 +98,6 @@ export class HaBackupSummaryProgress extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-backup-summary-progress": HaBackupSummaryProgress;
|
"ha-backup-overview-progress": HaBackupOverviewProgress;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
||||||
|
import { differenceInDays, setHours, setMinutes } from "date-fns";
|
||||||
|
import type { CSSResultGroup } from "lit";
|
||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||||
|
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
||||||
|
import "../../../../../components/ha-button";
|
||||||
|
import "../../../../../components/ha-card";
|
||||||
|
import "../../../../../components/ha-md-list";
|
||||||
|
import "../../../../../components/ha-md-list-item";
|
||||||
|
import "../../../../../components/ha-svg-icon";
|
||||||
|
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
||||||
|
import { BackupScheduleState } from "../../../../../data/backup";
|
||||||
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import "../ha-backup-summary-card";
|
||||||
|
|
||||||
|
@customElement("ha-backup-overview-summary")
|
||||||
|
class HaBackupOverviewBackups extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public config!: BackupConfig;
|
||||||
|
|
||||||
|
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
||||||
|
const sortedBackups = backups
|
||||||
|
.filter((backup) => backup.with_automatic_settings)
|
||||||
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
|
|
||||||
|
return sortedBackups[0] as BackupContent | undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
private _nextBackupDescription(schedule: BackupScheduleState) {
|
||||||
|
const newDate = setMinutes(setHours(new Date(), 4), 45);
|
||||||
|
const time = formatTime(newDate, this.hass.locale, this.hass.config);
|
||||||
|
|
||||||
|
switch (schedule) {
|
||||||
|
case BackupScheduleState.DAILY:
|
||||||
|
return `Next automatic backup tomorrow at ${time}`;
|
||||||
|
case BackupScheduleState.MONDAY:
|
||||||
|
return `Next automatic backup next Monday at ${time}`;
|
||||||
|
case BackupScheduleState.TUESDAY:
|
||||||
|
return `Next automatic backup next Thuesday at ${time}`;
|
||||||
|
case BackupScheduleState.WEDNESDAY:
|
||||||
|
return `Next automatic backup next Wednesday at ${time}`;
|
||||||
|
case BackupScheduleState.THURSDAY:
|
||||||
|
return `Next automatic backup next Thursday at ${time}`;
|
||||||
|
case BackupScheduleState.FRIDAY:
|
||||||
|
return `Next automatic backup next Friday at ${time}`;
|
||||||
|
case BackupScheduleState.SATURDAY:
|
||||||
|
return `Next automatic backup next Saturday at ${time}`;
|
||||||
|
case BackupScheduleState.SUNDAY:
|
||||||
|
return `Next automatic backup next Sunday at ${time}`;
|
||||||
|
default:
|
||||||
|
return "No automatic backup scheduled";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const lastBackup = this._lastBackup(this.backups);
|
||||||
|
|
||||||
|
if (!lastBackup) {
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card
|
||||||
|
heading="No automatic backup available"
|
||||||
|
description="You have no automatic backups yet."
|
||||||
|
status="warning"
|
||||||
|
>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastBackupDate = new Date(lastBackup.date);
|
||||||
|
|
||||||
|
const numberOfDays = differenceInDays(new Date(), lastBackupDate);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const lastBackupDescription = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and synced to ${lastBackup.agent_ids?.length} locations.`;
|
||||||
|
const nextBackupDescription = this._nextBackupDescription(
|
||||||
|
this.config.schedule.state
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastAttempt = this.config.last_attempted_automatic_backup
|
||||||
|
? new Date(this.config.last_attempted_automatic_backup)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (lastAttempt && lastAttempt > lastBackupDate) {
|
||||||
|
const lastAttemptDescription = `The last automatic backup trigged ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`;
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card
|
||||||
|
heading=${`Last automatic backup failed`}
|
||||||
|
status="error"
|
||||||
|
>
|
||||||
|
<ul class="list">
|
||||||
|
<li class="item">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
|
<span>${lastAttemptDescription}</span>
|
||||||
|
</li>
|
||||||
|
<li class="item">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
<span>${lastBackupDescription}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numberOfDays > 0) {
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card
|
||||||
|
heading=${`No backup for ${numberOfDays} days`}
|
||||||
|
status="warning"
|
||||||
|
>
|
||||||
|
<ul class="list">
|
||||||
|
<li class="item">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
|
<span>${lastBackupDescription}</span>
|
||||||
|
</li>
|
||||||
|
<li class="item">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
<span>${nextBackupDescription}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
||||||
|
<ul class="list">
|
||||||
|
<li class="item">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
|
<span>${lastBackupDescription}</span>
|
||||||
|
</li>
|
||||||
|
<li class="item">
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
<span>${nextBackupDescription}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 8px 24px 24px 24px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: 0.25px;
|
||||||
|
}
|
||||||
|
ha-svg-icon {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-backup-overview-summary": HaBackupOverviewBackups;
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
|
|
||||||
@state() private _step?: Step;
|
@state() private _step?: Step;
|
||||||
|
|
||||||
|
@state() private _steps: Step[] = [];
|
||||||
|
|
||||||
@state() private _params?: BackupOnboardingDialogParams;
|
@state() private _params?: BackupOnboardingDialogParams;
|
||||||
|
|
||||||
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
||||||
@ -90,7 +92,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
|
|
||||||
public showDialog(params: BackupOnboardingDialogParams): void {
|
public showDialog(params: BackupOnboardingDialogParams): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._step = STEPS[0];
|
this._steps = params.showIntro ? STEPS.concat() : STEPS.slice(1);
|
||||||
|
this._step = this._steps[0];
|
||||||
this._config = RECOMMENDED_CONFIG;
|
this._config = RECOMMENDED_CONFIG;
|
||||||
|
|
||||||
// Enable local location by default
|
// Enable local location by default
|
||||||
@ -117,6 +120,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
}
|
}
|
||||||
this._opened = false;
|
this._opened = false;
|
||||||
this._step = undefined;
|
this._step = undefined;
|
||||||
|
this._steps = [];
|
||||||
this._config = undefined;
|
this._config = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
}
|
}
|
||||||
@ -158,19 +162,19 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _previousStep() {
|
private _previousStep() {
|
||||||
const index = STEPS.indexOf(this._step!);
|
const index = this._steps.indexOf(this._step!);
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._step = STEPS[index - 1];
|
this._step = this._steps[index - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _nextStep() {
|
private _nextStep() {
|
||||||
const index = STEPS.indexOf(this._step!);
|
const index = this._steps.indexOf(this._step!);
|
||||||
if (index === STEPS.length - 1) {
|
if (index === this._steps.length - 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._step = STEPS[index + 1];
|
this._step = this._steps[index + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -178,8 +182,8 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLastStep = this._step === STEPS[STEPS.length - 1];
|
const isLastStep = this._step === this._steps[this._steps.length - 1];
|
||||||
const isFirstStep = this._step === STEPS[0];
|
const isFirstStep = this._step === this._steps[0];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { mdiClose, mdiCog, mdiPencil } from "@mdi/js";
|
import { mdiCalendarSync, mdiClose, mdiGestureTap } from "@mdi/js";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@ -7,6 +7,7 @@ import "../../../../components/ha-dialog-header";
|
|||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import "../../../../components/ha-icon-next";
|
import "../../../../components/ha-icon-next";
|
||||||
import "../../../../components/ha-md-dialog";
|
import "../../../../components/ha-md-dialog";
|
||||||
|
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||||
import "../../../../components/ha-md-list";
|
import "../../../../components/ha-md-list";
|
||||||
import "../../../../components/ha-md-list-item";
|
import "../../../../components/ha-md-list-item";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
@ -14,7 +15,6 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
|||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import type { NewBackupDialogParams } from "./show-dialog-new-backup";
|
import type { NewBackupDialogParams } from "./show-dialog-new-backup";
|
||||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
|
||||||
|
|
||||||
@customElement("ha-dialog-new-backup")
|
@customElement("ha-dialog-new-backup")
|
||||||
class DialogNewBackup extends LitElement implements HassDialog {
|
class DialogNewBackup extends LitElement implements HassDialog {
|
||||||
@ -75,7 +75,7 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
|||||||
type="button"
|
type="button"
|
||||||
.disabled=${!this._params.config.create_backup.password}
|
.disabled=${!this._params.config.create_backup.password}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||||
<span slot="headline">Automatic backup</span>
|
<span slot="headline">Automatic backup</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
Create a backup with the data and locations you have configured.
|
Create a backup with the data and locations you have configured.
|
||||||
@ -83,7 +83,7 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
|||||||
<ha-icon-next slot="end"></ha-icon-next>
|
<ha-icon-next slot="end"></ha-icon-next>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item @click=${this._manual} type="button">
|
<ha-md-list-item @click=${this._manual} type="button">
|
||||||
<ha-svg-icon slot="start" .path=${mdiPencil}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||||
<span slot="headline">Manual backup</span>
|
<span slot="headline">Manual backup</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
Select data and locations for a manual backup.
|
Select data and locations for a manual backup.
|
||||||
|
@ -4,6 +4,7 @@ import type { CloudStatus } from "../../../../data/cloud";
|
|||||||
export interface BackupOnboardingDialogParams {
|
export interface BackupOnboardingDialogParams {
|
||||||
submit?: (value: boolean) => void;
|
submit?: (value: boolean) => void;
|
||||||
cancel?: () => void;
|
cancel?: () => void;
|
||||||
|
showIntro?: boolean;
|
||||||
cloudStatus: CloudStatus;
|
cloudStatus: CloudStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ import {
|
|||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiUpload,
|
mdiUpload,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
import { storage } from "../../../common/decorators/storage";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
@ -36,11 +36,8 @@ import "../../../components/ha-svg-icon";
|
|||||||
import { getSignedPath } from "../../../data/auth";
|
import { getSignedPath } from "../../../data/auth";
|
||||||
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||||
import {
|
import {
|
||||||
compareAgents,
|
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
deleteBackup,
|
deleteBackup,
|
||||||
fetchBackupConfig,
|
|
||||||
fetchBackupInfo,
|
|
||||||
generateBackup,
|
generateBackup,
|
||||||
generateBackupWithAutomaticSettings,
|
generateBackupWithAutomaticSettings,
|
||||||
getBackupDownloadUrl,
|
getBackupDownloadUrl,
|
||||||
@ -48,10 +45,6 @@ import {
|
|||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
} from "../../../data/backup";
|
} from "../../../data/backup";
|
||||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||||
import {
|
|
||||||
DEFAULT_MANAGER_STATE,
|
|
||||||
subscribeBackupEvents,
|
|
||||||
} from "../../../data/backup_manager";
|
|
||||||
import type { CloudStatus } from "../../../data/cloud";
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||||
import {
|
import {
|
||||||
@ -66,7 +59,6 @@ import type { HomeAssistant, Route } from "../../../types";
|
|||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
import { bytesToString } from "../../../util/bytes-to-string";
|
import { bytesToString } from "../../../util/bytes-to-string";
|
||||||
import { fileDownload } from "../../../util/file_download";
|
import { fileDownload } from "../../../util/file_download";
|
||||||
import { showToast } from "../../../util/toast";
|
|
||||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||||
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
||||||
@ -89,14 +81,14 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
|
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||||
|
|
||||||
@state() private _backups: BackupContent[] = [];
|
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public config?: BackupConfig;
|
||||||
|
|
||||||
@state() private _selected: string[] = [];
|
@state() private _selected: string[] = [];
|
||||||
|
|
||||||
@state() private _config?: BackupConfig;
|
|
||||||
|
|
||||||
@storage({ key: "backups-table-grouping", state: false, subscribe: false })
|
@storage({ key: "backups-table-grouping", state: false, subscribe: false })
|
||||||
private _activeGrouping?: string = "formatted_type";
|
private _activeGrouping?: string = "formatted_type";
|
||||||
|
|
||||||
@ -107,8 +99,6 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed: string[] = [];
|
private _activeCollapsed: string[] = [];
|
||||||
|
|
||||||
private _subscribed?: Promise<() => void>;
|
|
||||||
|
|
||||||
@query("hass-tabs-subpage-data-table", true)
|
@query("hass-tabs-subpage-data-table", true)
|
||||||
private _dataTable!: HaTabsSubpageDataTable;
|
private _dataTable!: HaTabsSubpageDataTable;
|
||||||
|
|
||||||
@ -251,7 +241,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const backupInProgress =
|
const backupInProgress =
|
||||||
"state" in this._manager && this._manager.state === "in_progress";
|
"state" in this.manager && this.manager.state === "in_progress";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
@ -278,7 +268,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
@row-click=${this._showBackupDetails}
|
@row-click=${this._showBackupDetails}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(this.hass.localize)}
|
||||||
.data=${this._data(this._backups)}
|
.data=${this._data(this.backups)}
|
||||||
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
||||||
.searchLabel=${this.hass.localize(
|
.searchLabel=${this.hass.localize(
|
||||||
"ui.panel.config.backup.picker.search"
|
"ui.panel.config.backup.picker.search"
|
||||||
@ -351,82 +341,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _unsubscribeEvents() {
|
|
||||||
if (this._subscribed) {
|
|
||||||
this._subscribed.then((unsub) => unsub());
|
|
||||||
this._subscribed = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _subscribeEvents() {
|
|
||||||
this._unsubscribeEvents();
|
|
||||||
if (!this.isConnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._subscribed = subscribeBackupEvents(this.hass!, (event) => {
|
|
||||||
this._manager = event;
|
|
||||||
if ("state" in event) {
|
|
||||||
if (event.state === "completed" || event.state === "failed") {
|
|
||||||
this._fetchBackupInfo();
|
|
||||||
}
|
|
||||||
if (event.state === "failed") {
|
|
||||||
let message = "";
|
|
||||||
switch (this._manager.manager_state) {
|
|
||||||
case "create_backup":
|
|
||||||
message = "Failed to create backup";
|
|
||||||
break;
|
|
||||||
case "restore_backup":
|
|
||||||
message = "Failed to restore backup";
|
|
||||||
break;
|
|
||||||
case "receive_backup":
|
|
||||||
message = "Failed to upload backup";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (message) {
|
|
||||||
showToast(this, { message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._fetchBackupInfo();
|
|
||||||
this._subscribeEvents();
|
|
||||||
this._fetchBackupConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (this.hasUpdated) {
|
|
||||||
this._fetchBackupInfo();
|
|
||||||
this._subscribeEvents();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
this._unsubscribeEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchBackupInfo() {
|
|
||||||
const info = await fetchBackupInfo(this.hass);
|
|
||||||
this._backups = info.backups.map((backup) => ({
|
|
||||||
...backup,
|
|
||||||
agent_ids: backup.agent_ids?.sort(compareAgents),
|
|
||||||
failed_agent_ids: backup.failed_agent_ids?.sort(compareAgents),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchBackupConfig() {
|
|
||||||
const { config } = await fetchBackupConfig(this.hass);
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _needsOnboarding() {
|
private get _needsOnboarding() {
|
||||||
return !this._config?.create_backup.password;
|
return !this.config?.create_backup.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _uploadBackup(ev) {
|
private async _uploadBackup(ev) {
|
||||||
@ -438,7 +354,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _newBackup(): Promise<void> {
|
private async _newBackup(): Promise<void> {
|
||||||
const config = this._config!;
|
const config = this.config!;
|
||||||
|
|
||||||
const type = await showNewBackupDialog(this, { config });
|
const type = await showNewBackupDialog(this, { config });
|
||||||
|
|
||||||
@ -454,12 +370,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await generateBackup(this.hass, params);
|
await generateBackup(this.hass, params);
|
||||||
await this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === "automatic") {
|
if (type === "automatic") {
|
||||||
await generateBackupWithAutomaticSettings(this.hass);
|
await generateBackupWithAutomaticSettings(this.hass);
|
||||||
await this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +406,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await deleteBackup(this.hass, backup.backup_id);
|
await deleteBackup(this.hass, backup.backup_id);
|
||||||
this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _deleteSelected() {
|
private async _deleteSelected() {
|
||||||
@ -516,7 +432,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
this._dataTable.clearSelection();
|
this._dataTable.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
import type { TemplateResult } from "lit";
|
|
||||||
import { css, html, LitElement } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-icon-next";
|
|
||||||
import "../../../components/ha-md-list";
|
|
||||||
import "../../../components/ha-md-list-item";
|
|
||||||
import type { BackupAgent } from "../../../data/backup";
|
|
||||||
import { fetchBackupAgentsInfo } from "../../../data/backup";
|
|
||||||
import "../../../layouts/hass-subpage";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
|
||||||
import { domainToName } from "../../../data/integration";
|
|
||||||
|
|
||||||
@customElement("ha-config-backup-locations")
|
|
||||||
class HaConfigBackupLocations extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
|
||||||
|
|
||||||
@state() private _agents: BackupAgent[] = [];
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._fetchAgents();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<hass-subpage
|
|
||||||
back-path="/config/backup"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
.header=${this.hass.localize("ui.panel.config.backup.caption")}
|
|
||||||
>
|
|
||||||
<div class="content">
|
|
||||||
<div class="header">
|
|
||||||
<h2 class="title">Locations</h2>
|
|
||||||
<p class="description">
|
|
||||||
To keep your data safe it is recommended your backups is at least
|
|
||||||
on two different locations and one of them is off-site.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ha-card class="agents">
|
|
||||||
<div class="card-content">
|
|
||||||
${this._agents.length > 0
|
|
||||||
? html`
|
|
||||||
<ha-md-list>
|
|
||||||
${this._agents.map((agent) => {
|
|
||||||
const [domain, name] = agent.agent_id.split(".");
|
|
||||||
const domainName = domainToName(
|
|
||||||
this.hass.localize,
|
|
||||||
domain
|
|
||||||
);
|
|
||||||
return html`
|
|
||||||
<ha-md-list-item
|
|
||||||
type="link"
|
|
||||||
href="/config/backup/locations/${agent.agent_id}"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
.src=${brandsUrl({
|
|
||||||
domain,
|
|
||||||
type: "icon",
|
|
||||||
useFallback: true,
|
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
|
||||||
})}
|
|
||||||
crossorigin="anonymous"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
alt=""
|
|
||||||
slot="start"
|
|
||||||
/>
|
|
||||||
<div slot="headline">${domainName}: ${name}</div>
|
|
||||||
<ha-icon-next slot="end"></ha-icon-next>
|
|
||||||
</ha-md-list-item>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</ha-md-list>
|
|
||||||
`
|
|
||||||
: html`<p>No sync agents configured</p>`}
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
</hass-subpage>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchAgents() {
|
|
||||||
const data = await fetchBackupAgentsInfo(this.hass);
|
|
||||||
this._agents = data.agents;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
.content {
|
|
||||||
padding: 28px 20px 0;
|
|
||||||
max-width: 690px;
|
|
||||||
margin: 0 auto;
|
|
||||||
gap: 24px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .title {
|
|
||||||
font-size: 22px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 28px;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
margin: 0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header .description {
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: 0.25px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-md-list {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
ha-md-list-item img {
|
|
||||||
width: 48px;
|
|
||||||
}
|
|
||||||
.card-content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-config-backup-locations": HaConfigBackupLocations;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js";
|
import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||||
import "../../../components/ha-button";
|
import "../../../components/ha-button";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
@ -13,25 +14,24 @@ import "../../../components/ha-icon-overflow-menu";
|
|||||||
import "../../../components/ha-list-item";
|
import "../../../components/ha-list-item";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
fetchBackupConfig,
|
|
||||||
fetchBackupInfo,
|
|
||||||
generateBackup,
|
generateBackup,
|
||||||
generateBackupWithAutomaticSettings,
|
generateBackupWithAutomaticSettings,
|
||||||
type BackupConfig,
|
type BackupConfig,
|
||||||
type BackupContent,
|
type BackupContent,
|
||||||
} from "../../../data/backup";
|
} from "../../../data/backup";
|
||||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||||
import { DEFAULT_MANAGER_STATE } from "../../../data/backup_manager";
|
|
||||||
import type { CloudStatus } from "../../../data/cloud";
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import "./components/ha-backup-summary-card";
|
import "./components/ha-backup-summary-card";
|
||||||
import "./components/ha-backup-summary-progress";
|
|
||||||
import "./components/ha-backup-summary-status";
|
import "./components/ha-backup-summary-status";
|
||||||
import "./components/overview/ha-backup-overview-backups";
|
import "./components/overview/ha-backup-overview-backups";
|
||||||
|
import "./components/overview/ha-backup-overview-onboarding";
|
||||||
|
import "./components/overview/ha-backup-overview-progress";
|
||||||
import "./components/overview/ha-backup-overview-settings";
|
import "./components/overview/ha-backup-overview-settings";
|
||||||
|
import "./components/overview/ha-backup-overview-summary";
|
||||||
import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding";
|
import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding";
|
||||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||||
@ -47,27 +47,13 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
|
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||||
|
|
||||||
@state() private _backups: BackupContent[] = [];
|
@property({ attribute: false }) public backups: BackupContent[] = [];
|
||||||
|
|
||||||
@state() private _fetching = false;
|
@property({ attribute: false }) public fetching = false;
|
||||||
|
|
||||||
@state() private _config?: BackupConfig;
|
@property({ attribute: false }) public config?: BackupConfig;
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._fetchBackupInfo();
|
|
||||||
this._fetchBackupConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (this.hasUpdated) {
|
|
||||||
this._fetchBackupInfo();
|
|
||||||
this._fetchBackupConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _uploadBackup(ev) {
|
private async _uploadBackup(ev) {
|
||||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||||
@ -77,40 +63,36 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
await showUploadBackupDialog(this, {});
|
await showUploadBackupDialog(this, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setupAutomaticBackup() {
|
private _handleOnboardingButtonClick(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._setupAutomaticBackup(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _setupAutomaticBackup(showIntro: boolean) {
|
||||||
const success = await showBackupOnboardingDialog(this, {
|
const success = await showBackupOnboardingDialog(this, {
|
||||||
cloudStatus: this.cloudStatus,
|
cloudStatus: this.cloudStatus,
|
||||||
|
showIntro: showIntro,
|
||||||
});
|
});
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._fetchBackupConfig();
|
fireEvent(this, "ha-refresh-backup-config");
|
||||||
await generateBackupWithAutomaticSettings(this.hass);
|
await generateBackupWithAutomaticSettings(this.hass);
|
||||||
await this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchBackupInfo() {
|
|
||||||
const info = await fetchBackupInfo(this.hass);
|
|
||||||
this._backups = info.backups;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchBackupConfig() {
|
|
||||||
const { config } = await fetchBackupConfig(this.hass);
|
|
||||||
this._config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _newBackup(): Promise<void> {
|
private async _newBackup(): Promise<void> {
|
||||||
if (this._needsOnboarding) {
|
if (this._needsOnboarding) {
|
||||||
this._setupAutomaticBackup();
|
this._setupAutomaticBackup(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._config) {
|
if (!this.config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this._config;
|
const config = this.config;
|
||||||
|
|
||||||
const type = await showNewBackupDialog(this, { config });
|
const type = await showNewBackupDialog(this, { config });
|
||||||
|
|
||||||
@ -126,22 +108,22 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await generateBackup(this.hass, params);
|
await generateBackup(this.hass, params);
|
||||||
await this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type === "automatic") {
|
if (type === "automatic") {
|
||||||
await generateBackupWithAutomaticSettings(this.hass);
|
await generateBackupWithAutomaticSettings(this.hass);
|
||||||
await this._fetchBackupInfo();
|
fireEvent(this, "ha-refresh-backup-info");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _needsOnboarding() {
|
private get _needsOnboarding() {
|
||||||
return !this._config?.create_backup.password;
|
return !this.config?.create_backup.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const backupInProgress =
|
const backupInProgress =
|
||||||
"state" in this._manager && this._manager.state === "in_progress";
|
"state" in this.manager && this.manager.state === "in_progress";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage
|
<hass-subpage
|
||||||
@ -167,57 +149,50 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._fetching
|
${backupInProgress
|
||||||
? html`
|
? html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-overview-progress
|
||||||
heading="Loading backups"
|
.hass=${this.hass}
|
||||||
description="Your backup information is being retrieved."
|
.manager=${this.manager}
|
||||||
status="loading"
|
|
||||||
>
|
>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-overview-progress>
|
||||||
`
|
`
|
||||||
: backupInProgress
|
: this.fetching
|
||||||
? html`
|
? html`
|
||||||
<ha-backup-summary-progress
|
<ha-backup-summary-card
|
||||||
.hass=${this.hass}
|
heading="Loading backups"
|
||||||
.manager=${this._manager}
|
description="Your backup information is being retrieved."
|
||||||
|
status="loading"
|
||||||
>
|
>
|
||||||
</ha-backup-summary-progress>
|
</ha-backup-summary-card>
|
||||||
`
|
`
|
||||||
: this._needsOnboarding
|
: this._needsOnboarding
|
||||||
? html`
|
? html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-overview-onboarding
|
||||||
heading="Configure automatic backups"
|
.hass=${this.hass}
|
||||||
description="Have a one-click backup automation with selected data and locations."
|
@button-click=${this._handleOnboardingButtonClick}
|
||||||
has-action
|
|
||||||
status="info"
|
|
||||||
>
|
>
|
||||||
<ha-button
|
</ha-backup-overview-onboarding>
|
||||||
slot="action"
|
|
||||||
@click=${this._setupAutomaticBackup}
|
|
||||||
>
|
|
||||||
Set up automatic backups
|
|
||||||
</ha-button>
|
|
||||||
</ha-backup-summary-card>
|
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-backup-summary-status
|
<ha-backup-overview-summary
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.backups=${this._backups}
|
.backups=${this.backups}
|
||||||
|
.config=${this.config}
|
||||||
>
|
>
|
||||||
</ha-backup-summary-status>
|
</ha-backup-overview-summary>
|
||||||
`}
|
`}
|
||||||
|
|
||||||
<ha-backup-overview-backups
|
<ha-backup-overview-backups
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.backups=${this._backups}
|
.backups=${this.backups}
|
||||||
></ha-backup-overview-backups>
|
></ha-backup-overview-backups>
|
||||||
|
|
||||||
${!this._needsOnboarding
|
${!this._needsOnboarding
|
||||||
? html`
|
? html`
|
||||||
<ha-backup-overview-settings
|
<ha-backup-overview-settings
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.config=${this._config!}
|
.config=${this.config!}
|
||||||
></ha-backup-overview-settings>
|
></ha-backup-overview-settings>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
@ -258,6 +233,10 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
ha-fab[disabled] {
|
||||||
|
--mdc-theme-secondary: var(--disabled-text-color) !important;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { debounce } from "../../../common/util/debounce";
|
import { debounce } from "../../../common/util/debounce";
|
||||||
import "../../../components/ha-button";
|
import "../../../components/ha-button";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
import "../../../components/ha-password-field";
|
import "../../../components/ha-password-field";
|
||||||
import "../../../components/ha-settings-row";
|
|
||||||
import type { BackupConfig } from "../../../data/backup";
|
import type { BackupConfig } from "../../../data/backup";
|
||||||
import {
|
import { updateBackupConfig } from "../../../data/backup";
|
||||||
BackupScheduleState,
|
|
||||||
fetchBackupConfig,
|
|
||||||
updateBackupConfig,
|
|
||||||
} from "../../../data/backup";
|
|
||||||
import type { CloudStatus } from "../../../data/cloud";
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import "../../../layouts/hass-subpage";
|
import "../../../layouts/hass-subpage";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
@ -22,27 +19,6 @@ import "./components/config/ha-backup-config-encryption-key";
|
|||||||
import "./components/config/ha-backup-config-schedule";
|
import "./components/config/ha-backup-config-schedule";
|
||||||
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
|
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
|
||||||
|
|
||||||
const INITIAL_BACKUP_CONFIG: BackupConfig = {
|
|
||||||
create_backup: {
|
|
||||||
agent_ids: [],
|
|
||||||
include_folders: [],
|
|
||||||
include_database: true,
|
|
||||||
include_addons: [],
|
|
||||||
include_all_addons: true,
|
|
||||||
password: null,
|
|
||||||
name: null,
|
|
||||||
},
|
|
||||||
retention: {
|
|
||||||
copies: 3,
|
|
||||||
days: null,
|
|
||||||
},
|
|
||||||
schedule: {
|
|
||||||
state: BackupScheduleState.DAILY,
|
|
||||||
},
|
|
||||||
last_attempted_automatic_backup: null,
|
|
||||||
last_completed_automatic_backup: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("ha-config-backup-settings")
|
@customElement("ha-config-backup-settings")
|
||||||
class HaConfigBackupSettings extends LitElement {
|
class HaConfigBackupSettings extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -51,22 +27,19 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@state() private _backupConfig: BackupConfig = INITIAL_BACKUP_CONFIG;
|
@property({ attribute: false }) public config?: BackupConfig;
|
||||||
|
|
||||||
protected willUpdate(changedProps) {
|
@state() private _config?: BackupConfig;
|
||||||
super.willUpdate(changedProps);
|
|
||||||
if (!this.hasUpdated) {
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
this._fetchData();
|
super.willUpdate(changedProperties);
|
||||||
|
if (changedProperties.has("config") && !this._config) {
|
||||||
|
this._config = this.config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchData() {
|
|
||||||
const { config } = await fetchBackupConfig(this.hass);
|
|
||||||
this._backupConfig = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._backupConfig) {
|
if (!this._config) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +60,7 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
<ha-backup-config-schedule
|
<ha-backup-config-schedule
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._backupConfig}
|
.value=${this._config}
|
||||||
@value-changed=${this._scheduleConfigChanged}
|
@value-changed=${this._scheduleConfigChanged}
|
||||||
></ha-backup-config-schedule>
|
></ha-backup-config-schedule>
|
||||||
</div>
|
</div>
|
||||||
@ -113,7 +86,7 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
<ha-backup-config-agents
|
<ha-backup-config-agents
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._backupConfig.create_backup.agent_ids}
|
.value=${this._config.create_backup.agent_ids}
|
||||||
.cloudStatus=${this.cloudStatus}
|
.cloudStatus=${this.cloudStatus}
|
||||||
@value-changed=${this._agentsConfigChanged}
|
@value-changed=${this._agentsConfigChanged}
|
||||||
></ha-backup-config-agents>
|
></ha-backup-config-agents>
|
||||||
@ -130,7 +103,7 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
<ha-backup-config-encryption-key
|
<ha-backup-config-encryption-key
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._backupConfig.create_backup.password}
|
.value=${this._config.create_backup.password}
|
||||||
@value-changed=${this._encryptionKeyChanged}
|
@value-changed=${this._encryptionKeyChanged}
|
||||||
></ha-backup-config-encryption-key>
|
></ha-backup-config-encryption-key>
|
||||||
</div>
|
</div>
|
||||||
@ -142,8 +115,8 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
|
|
||||||
private _scheduleConfigChanged(ev) {
|
private _scheduleConfigChanged(ev) {
|
||||||
const value = ev.detail.value as BackupConfigSchedule;
|
const value = ev.detail.value as BackupConfigSchedule;
|
||||||
this._backupConfig = {
|
this._config = {
|
||||||
...this._backupConfig,
|
...this._config!,
|
||||||
schedule: value.schedule,
|
schedule: value.schedule,
|
||||||
retention: value.retention,
|
retention: value.retention,
|
||||||
};
|
};
|
||||||
@ -156,7 +129,7 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
include_all_addons,
|
include_all_addons,
|
||||||
include_database,
|
include_database,
|
||||||
include_folders,
|
include_folders,
|
||||||
} = this._backupConfig.create_backup;
|
} = this._config!.create_backup;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
include_homeassistant: true,
|
include_homeassistant: true,
|
||||||
@ -169,10 +142,10 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
|
|
||||||
private _dataConfigChanged(ev) {
|
private _dataConfigChanged(ev) {
|
||||||
const data = ev.detail.value as BackupConfigData;
|
const data = ev.detail.value as BackupConfigData;
|
||||||
this._backupConfig = {
|
this._config = {
|
||||||
...this._backupConfig,
|
...this._config!,
|
||||||
create_backup: {
|
create_backup: {
|
||||||
...this._backupConfig.create_backup,
|
...this.config!.create_backup,
|
||||||
include_database: data.include_database,
|
include_database: data.include_database,
|
||||||
include_folders: data.include_folders || null,
|
include_folders: data.include_folders || null,
|
||||||
include_all_addons: data.include_all_addons,
|
include_all_addons: data.include_all_addons,
|
||||||
@ -184,10 +157,10 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
|
|
||||||
private _agentsConfigChanged(ev) {
|
private _agentsConfigChanged(ev) {
|
||||||
const agents = ev.detail.value as string[];
|
const agents = ev.detail.value as string[];
|
||||||
this._backupConfig = {
|
this._config = {
|
||||||
...this._backupConfig,
|
...this._config!,
|
||||||
create_backup: {
|
create_backup: {
|
||||||
...this._backupConfig.create_backup,
|
...this._config!.create_backup,
|
||||||
agent_ids: agents,
|
agent_ids: agents,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -196,10 +169,10 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
|
|
||||||
private _encryptionKeyChanged(ev) {
|
private _encryptionKeyChanged(ev) {
|
||||||
const password = ev.detail.value as string;
|
const password = ev.detail.value as string;
|
||||||
this._backupConfig = {
|
this._config = {
|
||||||
...this._backupConfig,
|
...this._config!,
|
||||||
create_backup: {
|
create_backup: {
|
||||||
...this._backupConfig.create_backup,
|
...this._config!.create_backup,
|
||||||
password: password,
|
password: password,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -211,16 +184,17 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
private async _save() {
|
private async _save() {
|
||||||
await updateBackupConfig(this.hass, {
|
await updateBackupConfig(this.hass, {
|
||||||
create_backup: {
|
create_backup: {
|
||||||
agent_ids: this._backupConfig.create_backup.agent_ids,
|
agent_ids: this._config!.create_backup.agent_ids,
|
||||||
include_folders: this._backupConfig.create_backup.include_folders ?? [],
|
include_folders: this._config!.create_backup.include_folders ?? [],
|
||||||
include_database: this._backupConfig.create_backup.include_database,
|
include_database: this._config!.create_backup.include_database,
|
||||||
include_addons: this._backupConfig.create_backup.include_addons ?? [],
|
include_addons: this._config!.create_backup.include_addons ?? [],
|
||||||
include_all_addons: this._backupConfig.create_backup.include_all_addons,
|
include_all_addons: this._config!.create_backup.include_all_addons,
|
||||||
password: this._backupConfig.create_backup.password,
|
password: this._config!.create_backup.password,
|
||||||
},
|
},
|
||||||
retention: this._backupConfig.retention,
|
retention: this._config!.retention,
|
||||||
schedule: this._backupConfig.schedule.state,
|
schedule: this._config!.schedule.state,
|
||||||
});
|
});
|
||||||
|
fireEvent(this, "ha-refresh-backup-config");
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -233,14 +207,6 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
ha-settings-row {
|
|
||||||
--settings-row-prefix-display: flex;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
ha-settings-row > ha-svg-icon {
|
|
||||||
align-self: center;
|
|
||||||
margin-inline-end: 16px;
|
|
||||||
}
|
|
||||||
.alert {
|
.alert {
|
||||||
--mdc-theme-primary: var(--error-color);
|
--mdc-theme-primary: var(--error-color);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,89 @@
|
|||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||||
|
import {
|
||||||
|
DEFAULT_MANAGER_STATE,
|
||||||
|
subscribeBackupEvents,
|
||||||
|
} from "../../../data/backup_manager";
|
||||||
import type { CloudStatus } from "../../../data/cloud";
|
import type { CloudStatus } from "../../../data/cloud";
|
||||||
import type { RouterOptions } from "../../../layouts/hass-router-page";
|
import type { RouterOptions } from "../../../layouts/hass-router-page";
|
||||||
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
import { HassRouterPage } from "../../../layouts/hass-router-page";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import "./ha-config-backup-overview";
|
import { showToast } from "../../../util/toast";
|
||||||
import "./ha-config-backup-backups";
|
import "./ha-config-backup-backups";
|
||||||
|
import "./ha-config-backup-overview";
|
||||||
|
import type { BackupConfig, BackupContent } from "../../../data/backup";
|
||||||
|
import {
|
||||||
|
compareAgents,
|
||||||
|
fetchBackupConfig,
|
||||||
|
fetchBackupInfo,
|
||||||
|
} from "../../../data/backup";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"ha-refresh-backup-info": undefined;
|
||||||
|
"ha-refresh-backup-config": undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("ha-config-backup")
|
@customElement("ha-config-backup")
|
||||||
class HaConfigBackup extends HassRouterPage {
|
class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@state() private _manager: ManagerStateEvent = DEFAULT_MANAGER_STATE;
|
||||||
|
|
||||||
|
@state() private _backups: BackupContent[] = [];
|
||||||
|
|
||||||
|
@state() private _fetching = false;
|
||||||
|
|
||||||
|
@state() private _config?: BackupConfig;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._fetching = true;
|
||||||
|
Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally(
|
||||||
|
() => {
|
||||||
|
this._fetching = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addEventListener("ha-refresh-backup-info", () => {
|
||||||
|
this._fetchBackupInfo();
|
||||||
|
});
|
||||||
|
this.addEventListener("ha-refresh-backup-config", () => {
|
||||||
|
this._fetchBackupConfig();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this.hasUpdated) {
|
||||||
|
this._fetchBackupInfo();
|
||||||
|
this._fetchBackupConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchBackupInfo() {
|
||||||
|
const info = await fetchBackupInfo(this.hass);
|
||||||
|
this._backups = info.backups.map((backup) => ({
|
||||||
|
...backup,
|
||||||
|
agent_ids: backup.agent_ids?.sort(compareAgents),
|
||||||
|
failed_agent_ids: backup.failed_agent_ids?.sort(compareAgents),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchBackupConfig() {
|
||||||
|
const { config } = await fetchBackupConfig(this.hass);
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
protected routerOptions: RouterOptions = {
|
protected routerOptions: RouterOptions = {
|
||||||
defaultPage: "overview",
|
defaultPage: "overview",
|
||||||
routes: {
|
routes: {
|
||||||
@ -31,10 +99,6 @@ class HaConfigBackup extends HassRouterPage {
|
|||||||
tag: "ha-config-backup-details",
|
tag: "ha-config-backup-details",
|
||||||
load: () => import("./ha-config-backup-details"),
|
load: () => import("./ha-config-backup-details"),
|
||||||
},
|
},
|
||||||
locations: {
|
|
||||||
tag: "ha-config-backup-locations",
|
|
||||||
load: () => import("./ha-config-backup-locations"),
|
|
||||||
},
|
|
||||||
settings: {
|
settings: {
|
||||||
tag: "ha-config-backup-settings",
|
tag: "ha-config-backup-settings",
|
||||||
load: () => import("./ha-config-backup-settings"),
|
load: () => import("./ha-config-backup-settings"),
|
||||||
@ -47,7 +111,12 @@ class HaConfigBackup extends HassRouterPage {
|
|||||||
pageEl.route = this.routeTail;
|
pageEl.route = this.routeTail;
|
||||||
pageEl.narrow = this.narrow;
|
pageEl.narrow = this.narrow;
|
||||||
pageEl.cloudStatus = this.cloudStatus;
|
pageEl.cloudStatus = this.cloudStatus;
|
||||||
|
pageEl.manager = this._manager;
|
||||||
|
pageEl.backups = this._backups;
|
||||||
|
pageEl.config = this._config;
|
||||||
|
pageEl.fetching = this._fetching;
|
||||||
|
|
||||||
|
pageEl.addEventListener("reload", () => {});
|
||||||
if (
|
if (
|
||||||
(!changedProps || changedProps.has("route")) &&
|
(!changedProps || changedProps.has("route")) &&
|
||||||
this._currentPage === "details"
|
this._currentPage === "details"
|
||||||
@ -55,6 +124,36 @@ class HaConfigBackup extends HassRouterPage {
|
|||||||
pageEl.backupId = this.routeTail.path.substr(1);
|
pageEl.backupId = this.routeTail.path.substr(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
|
||||||
|
return [
|
||||||
|
subscribeBackupEvents(this.hass!, (event) => {
|
||||||
|
this._manager = event;
|
||||||
|
if ("state" in event) {
|
||||||
|
if (event.state === "completed" || event.state === "failed") {
|
||||||
|
this._fetchBackupInfo();
|
||||||
|
}
|
||||||
|
if (event.state === "failed") {
|
||||||
|
let message = "";
|
||||||
|
switch (this._manager.manager_state) {
|
||||||
|
case "create_backup":
|
||||||
|
message = "Failed to create backup";
|
||||||
|
break;
|
||||||
|
case "restore_backup":
|
||||||
|
message = "Failed to restore backup";
|
||||||
|
break;
|
||||||
|
case "receive_backup":
|
||||||
|
message = "Failed to upload backup";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (message) {
|
||||||
|
showToast(this, { message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user