Skip to content

Commit

Permalink
Setup backup overview page (#23331)
Browse files Browse the repository at this point in the history
* Add overview page

* Remove configure button

* Reorganize files

* Add backups summary

* Add settings overview

* Fixes

* Update wording

* Setup onboarding before creating a backup
  • Loading branch information
piitaya authored Dec 19, 2024
1 parent 3da13b8 commit 95559cb
Show file tree
Hide file tree
Showing 15 changed files with 681 additions and 189 deletions.
2 changes: 1 addition & 1 deletion src/data/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export const getPreferredAgentForDownload = (agents: string[]) => {
};

export const CORE_LOCAL_AGENT = "backup.local";
export const HASSIO_LOCAL_AGENT = "backup.hassio";
export const HASSIO_LOCAL_AGENT = "hassio.local";
export const CLOUD_AGENT = "cloud.cloud";

export const isLocalAgent = (agentId: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ import { mdiDatabase } from "@mdi/js";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-switch";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { computeDomain } from "../../../../../common/entity/compute_domain";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-svg-icon";
import "../../../../../components/ha-switch";
import {
CLOUD_AGENT,
compareAgents,
computeBackupAgentName,
fetchBackupAgentsInfo,
isLocalAgent,
} from "../../../../data/backup";
import type { CloudStatus } from "../../../../data/cloud";
import type { HomeAssistant } from "../../../../types";
import { brandsUrl } from "../../../../util/brands-url";
} from "../../../../../data/backup";
import type { CloudStatus } from "../../../../../data/cloud";
import type { HomeAssistant } from "../../../../../types";
import { brandsUrl } from "../../../../../util/brands-url";

const DEFAULT_AGENTS = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-md-select";
import type { HaMdSelect } from "../../../../components/ha-md-select";
import "../../../../components/ha-md-select-option";
import "../../../../components/ha-switch";
import type { HaSwitch } from "../../../../components/ha-switch";
import { fetchHassioAddonsInfo } from "../../../../data/hassio/addon";
import type { HomeAssistant } from "../../../../types";
import "./ha-backup-addons-picker";
import type { BackupAddonItem } from "./ha-backup-addons-picker";
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-button";
import "../../../../../components/ha-expansion-panel";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-md-select";
import type { HaMdSelect } from "../../../../../components/ha-md-select";
import "../../../../../components/ha-md-select-option";
import "../../../../../components/ha-switch";
import type { HaSwitch } from "../../../../../components/ha-switch";
import { fetchHassioAddonsInfo } from "../../../../../data/hassio/addon";
import type { HomeAssistant } from "../../../../../types";
import "../ha-backup-addons-picker";
import type { BackupAddonItem } from "../ha-backup-addons-picker";

export type FormData = {
homeassistant: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { mdiDownload } from "@mdi/js";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import type { HomeAssistant } from "../../../../types";
import { showChangeBackupEncryptionKeyDialog } from "../dialogs/show-dialog-change-backup-encryption-key";
import { fileDownload } from "../../../../util/file_download";
import { showSetBackupEncryptionKeyDialog } from "../dialogs/show-dialog-set-backup-encryption-key";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import type { HomeAssistant } from "../../../../../types";
import { showChangeBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-change-backup-encryption-key";
import { fileDownload } from "../../../../../util/file_download";
import { showSetBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-set-backup-encryption-key";

@customElement("ha-backup-config-encryption-key")
class HaBackupConfigEncryptionKey extends LitElement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { HaCheckbox } from "../../../../components/ha-checkbox";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-md-select";
import "../../../../components/ha-md-textfield";
import type { HaMdSelect } from "../../../../components/ha-md-select";
import "../../../../components/ha-md-select-option";
import "../../../../components/ha-switch";
import type { BackupConfig } from "../../../../data/backup";
import { BackupScheduleState } from "../../../../data/backup";
import type { HomeAssistant } from "../../../../types";
import { clamp } from "../../../../common/number/clamp";
import { fireEvent } from "../../../../../common/dom/fire_event";
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-md-select";
import "../../../../../components/ha-md-textfield";
import type { HaMdSelect } from "../../../../../components/ha-md-select";
import "../../../../../components/ha-md-select-option";
import "../../../../../components/ha-switch";
import type { BackupConfig } from "../../../../../data/backup";
import { BackupScheduleState } from "../../../../../data/backup";
import type { HomeAssistant } from "../../../../../types";
import { clamp } from "../../../../../common/number/clamp";

export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;

Expand All @@ -24,7 +24,7 @@ const MAX_VALUE = 50;
enum RetentionPreset {
COPIES_3 = "copies_3",
DAYS_7 = "days_7",
FOREOVER = "forever",
FOREVER = "forever",
CUSTOM = "custom",
}

Expand Down Expand Up @@ -191,7 +191,7 @@ class HaBackupConfigSchedule extends LitElement {
<ha-md-select-option .value=${RetentionPreset.DAYS_7}>
<div slot="headline">Keep 7 days</div>
</ha-md-select-option>
<ha-md-select-option .value=${RetentionPreset.FOREOVER}>
<ha-md-select-option .value=${RetentionPreset.FOREVER}>
<div slot="headline">Keep forever</div>
</ha-md-select-option>
<ha-md-select-option .value=${RetentionPreset.CUSTOM}>
Expand Down Expand Up @@ -270,7 +270,9 @@ class HaBackupConfigSchedule extends LitElement {
const data = this._getData(this.value);
const retention = RETENTION_PRESETS[value];
// Ensure we have at least 1 in defaut value because user can't select 0
retention.value = Math.max(retention.value, 1);
if (value !== RetentionPreset.FOREVER) {
retention.value = Math.max(retention.value, 1);
}
this._setData({
...data,
retention: RETENTION_PRESETS[value],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import type { CSSResultGroup } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/ha-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
import "../../../../../components/ha-md-list";
import "../../../../../components/ha-md-list-item";
import type { BackupContent } from "../../../../../data/backup";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
import { bytesToString } from "../../../../../util/bytes-to-string";

type BackupStats = {
count: number;
size: number;
};

const computeBackupStats = (backups: BackupContent[]): BackupStats =>
backups.reduce(
(stats, backup) => {
stats.count++;
stats.size += backup.size;
return stats;
},
{ count: 0, size: 0 }
);

@customElement("ha-backup-overview-backups")
class HaBackupOverviewBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

@property({ attribute: false }) public backups: BackupContent[] = [];

private _showAll() {
navigate("/config/backup/backups");
}

private _automaticStats = memoizeOne((backups: BackupContent[]) => {
const automaticBackups = backups.filter(
(backup) => backup.with_automatic_settings
);
return computeBackupStats(automaticBackups);
});

private _manualStats = memoizeOne((backups: BackupContent[]) => {
const manualBackups = backups.filter(
(backup) => !backup.with_automatic_settings
);
return computeBackupStats(manualBackups);
});

render() {
const automaticStats = this._automaticStats(this.backups);
const manualStats = this._manualStats(this.backups);

return html`
<ha-card class="my-backups">
<div class="card-header">My backups</div>
<div class="card-content">
<ha-md-list>
<ha-md-list-item type="link" href="/config/backup/backups">
<div slot="headline">
${automaticStats.count} automatic backups
</div>
<div slot="supporting-text">
${bytesToString(automaticStats.size, 1)} in total
</div>
<ha-icon-next slot="end"></ha-icon-next>
</ha-md-list-item>
<ha-md-list-item type="link" href="/config/backup/backups">
<div slot="headline">${manualStats.count} manual backups</div>
<div slot="supporting-text">
${bytesToString(manualStats.size, 1)} in total
</div>
<ha-icon-next slot="end"></ha-icon-next>
</ha-md-list-item>
</ha-md-list>
</div>
<div class="card-actions">
<ha-button href="/config/backup/backups" @click=${this._showAll}>
Show all backups
</ha-button>
</div>
</ha-card>
`;
}

static get styles(): CSSResultGroup {
return [
haStyle,
css`
.content {
padding: 28px 20px 0;
max-width: 690px;
margin: 0 auto;
gap: 24px;
display: flex;
flex-direction: column;
margin-bottom: 24px;
margin-bottom: 72px;
}
.card-actions {
display: flex;
justify-content: flex-end;
}
.card-content {
padding-left: 0;
padding-right: 0;
}
`,
];
}
}

declare global {
interface HTMLElementTagNameMap {
"ha-backup-overview-backups": HaBackupOverviewBackups;
}
}
Loading

0 comments on commit 95559cb

Please sign in to comment.