mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Backup retention by location (#25144)
This commit is contained in:
parent
672fbc6007
commit
1b79869c87
@ -2,6 +2,7 @@ import { memoize } from "@fullcalendar/core/internal";
|
||||
import { setHours, setMinutes } from "date-fns";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import memoizeOne from "memoize-one";
|
||||
import checkValidDate from "../common/datetime/check_valid_date";
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDateTimeNumeric,
|
||||
@ -10,11 +11,10 @@ import { formatTime } from "../common/datetime/format_time";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { fileDownload } from "../util/file_download";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import type { BackupManagerState, ManagerStateEvent } from "./backup_manager";
|
||||
import { domainToName } from "./integration";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
import type { BackupManagerState, ManagerStateEvent } from "./backup_manager";
|
||||
import checkValidDate from "../common/datetime/check_valid_date";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
|
||||
export const enum BackupScheduleRecurrence {
|
||||
NEVER = "never",
|
||||
@ -37,6 +37,11 @@ export const BACKUP_DAYS: BackupDay[] = [
|
||||
export const sortWeekdays = (weekdays) =>
|
||||
weekdays.sort((a, b) => BACKUP_DAYS.indexOf(a) - BACKUP_DAYS.indexOf(b));
|
||||
|
||||
export interface Retention {
|
||||
copies?: number | null;
|
||||
days?: number | null;
|
||||
}
|
||||
|
||||
export interface BackupConfig {
|
||||
automatic_backups_configured: boolean;
|
||||
last_attempted_automatic_backup: string | null;
|
||||
@ -52,10 +57,7 @@ export interface BackupConfig {
|
||||
name: string | null;
|
||||
password: string | null;
|
||||
};
|
||||
retention: {
|
||||
copies?: number | null;
|
||||
days?: number | null;
|
||||
};
|
||||
retention: Retention;
|
||||
schedule: {
|
||||
recurrence: BackupScheduleRecurrence;
|
||||
time?: string | null;
|
||||
@ -75,10 +77,7 @@ export interface BackupMutableConfig {
|
||||
name?: string | null;
|
||||
password?: string | null;
|
||||
};
|
||||
retention?: {
|
||||
copies?: number | null;
|
||||
days?: number | null;
|
||||
};
|
||||
retention?: Retention;
|
||||
schedule?: {
|
||||
recurrence: BackupScheduleRecurrence;
|
||||
time?: string | null;
|
||||
@ -90,7 +89,8 @@ export interface BackupMutableConfig {
|
||||
export type BackupAgentsConfig = Record<string, BackupAgentConfig>;
|
||||
|
||||
export interface BackupAgentConfig {
|
||||
protected: boolean;
|
||||
protected?: boolean;
|
||||
retention?: Retention | null;
|
||||
}
|
||||
|
||||
export interface BackupAgent {
|
||||
|
@ -0,0 +1,274 @@
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { clamp } from "../../../../../common/number/clamp";
|
||||
import "../../../../../components/ha-expansion-panel";
|
||||
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-md-textfield";
|
||||
import type { BackupConfig, Retention } from "../../../../../data/backup";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;
|
||||
|
||||
const MIN_VALUE = 1;
|
||||
const MAX_VALUE = 9999; // because of input width
|
||||
|
||||
export enum RetentionPreset {
|
||||
GLOBAL = "global",
|
||||
COPIES_3 = "copies_3",
|
||||
FOREVER = "forever",
|
||||
CUSTOM = "custom",
|
||||
}
|
||||
|
||||
const PRESET_MAP: Record<
|
||||
Exclude<RetentionPreset, RetentionPreset.CUSTOM>,
|
||||
Retention | null
|
||||
> = {
|
||||
copies_3: { copies: 3, days: null },
|
||||
forever: { copies: null, days: null },
|
||||
global: null,
|
||||
};
|
||||
|
||||
export interface RetentionData {
|
||||
type: "copies" | "days" | "forever";
|
||||
value: number;
|
||||
}
|
||||
|
||||
@customElement("ha-backup-config-retention")
|
||||
class HaBackupConfigRetention extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public retention?: Retention | null;
|
||||
|
||||
@property() public headline?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "location-specific" })
|
||||
public locationSpecific = false;
|
||||
|
||||
@state() private _preset: RetentionPreset = RetentionPreset.COPIES_3;
|
||||
|
||||
@state() private _type: "copies" | "days" = "copies";
|
||||
|
||||
@state() private _value = 3;
|
||||
|
||||
private presetOptions = [
|
||||
RetentionPreset.COPIES_3,
|
||||
RetentionPreset.FOREVER,
|
||||
RetentionPreset.CUSTOM,
|
||||
];
|
||||
|
||||
public willUpdate(properties: PropertyValues) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
if (!this.retention) {
|
||||
this._preset = RetentionPreset.GLOBAL;
|
||||
} else if (
|
||||
this.retention?.days === null &&
|
||||
this.retention?.copies === null
|
||||
) {
|
||||
this._preset = RetentionPreset.FOREVER;
|
||||
} else {
|
||||
this._value = this.retention.copies || this.retention.days || 3;
|
||||
if (
|
||||
this.retention.days ||
|
||||
this.locationSpecific ||
|
||||
this.retention.copies !== 3
|
||||
) {
|
||||
this._preset = RetentionPreset.CUSTOM;
|
||||
this._type = this.retention?.copies ? "copies" : "days";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.locationSpecific) {
|
||||
this.presetOptions = [
|
||||
RetentionPreset.GLOBAL,
|
||||
RetentionPreset.FOREVER,
|
||||
RetentionPreset.CUSTOM,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.headline ??
|
||||
this.hass.localize(`ui.panel.config.backup.schedule.retention`)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.schedule.retention_description`
|
||||
)}
|
||||
</span>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@change=${this._retentionPresetChanged}
|
||||
.value=${this._preset}
|
||||
>
|
||||
${this.presetOptions.map(
|
||||
(option) => html`
|
||||
<ha-md-select-option .value=${option}>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.schedule.retention_presets.${option}`
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
`
|
||||
)}
|
||||
</ha-md-select>
|
||||
</ha-md-list-item>
|
||||
|
||||
${this._preset === RetentionPreset.CUSTOM
|
||||
? html`<ha-expansion-panel
|
||||
expanded
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.custom_retention"
|
||||
)}
|
||||
outlined
|
||||
>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.custom_retention_label"
|
||||
)}
|
||||
</span>
|
||||
<ha-md-textfield
|
||||
slot="end"
|
||||
@change=${this._retentionValueChanged}
|
||||
.value=${this._value.toString()}
|
||||
id="value"
|
||||
type="number"
|
||||
.min=${MIN_VALUE.toString()}
|
||||
.max=${MAX_VALUE.toString()}
|
||||
step="1"
|
||||
>
|
||||
</ha-md-textfield>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@change=${this._retentionTypeChanged}
|
||||
.value=${this._type}
|
||||
id="type"
|
||||
>
|
||||
<ha-md-select-option value="days">
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.retention_units.days"
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option value="copies">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.retention_units.copies"
|
||||
)}
|
||||
</ha-md-select-option>
|
||||
</ha-md-select>
|
||||
</ha-md-list-item></ha-expansion-panel
|
||||
> `
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
private _retentionPresetChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget as HaMdSelect;
|
||||
let value = target.value as RetentionPreset;
|
||||
|
||||
if (
|
||||
value === RetentionPreset.CUSTOM &&
|
||||
(this.locationSpecific || this._preset === RetentionPreset.FOREVER)
|
||||
) {
|
||||
this._preset = value;
|
||||
// custom needs to have a type of days or copies, set it to default copies 3
|
||||
value = RetentionPreset.COPIES_3;
|
||||
} else {
|
||||
this._preset = value;
|
||||
}
|
||||
|
||||
if (this.locationSpecific || value !== RetentionPreset.CUSTOM) {
|
||||
const retention = PRESET_MAP[value];
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: retention,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _retentionValueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget as HaMdSelect;
|
||||
const value = parseInt(target.value);
|
||||
const clamped = clamp(value, MIN_VALUE, MAX_VALUE);
|
||||
target.value = clamped.toString();
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
copies: this._type === "copies" ? clamped : null,
|
||||
days: this._type === "days" ? clamped : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _retentionTypeChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget as HaMdSelect;
|
||||
const type = target.value as "copies" | "days";
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
copies: type === "copies" ? this._value : null,
|
||||
days: type === "days" ? this._value : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
@media all and (max-width: 450px) {
|
||||
ha-md-select {
|
||||
min-width: 160px;
|
||||
width: 160px;
|
||||
--md-filled-field-content-space: 0;
|
||||
}
|
||||
}
|
||||
ha-md-textfield#value {
|
||||
min-width: 70px;
|
||||
}
|
||||
ha-md-select#type {
|
||||
min-width: 100px;
|
||||
}
|
||||
@media all and (max-width: 450px) {
|
||||
ha-md-textfield#value {
|
||||
min-width: 60px;
|
||||
margin: 0 -8px;
|
||||
}
|
||||
ha-md-select#type {
|
||||
min-width: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
--expansion-panel-content-padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-md-list-item.days {
|
||||
--md-item-align-items: flex-start;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-backup-config-retention": HaBackupConfigRetention;
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { clamp } from "../../../../../common/number/clamp";
|
||||
import "../../../../../components/ha-checkbox";
|
||||
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
|
||||
import "../../../../../components/ha-expansion-panel";
|
||||
@ -15,10 +13,13 @@ import "../../../../../components/ha-md-select";
|
||||
import type { HaMdSelect } from "../../../../../components/ha-md-select";
|
||||
import "../../../../../components/ha-md-select-option";
|
||||
import "../../../../../components/ha-md-textfield";
|
||||
import "../../../../../components/ha-switch";
|
||||
import "../../../../../components/ha-time-input";
|
||||
import "../../../../../components/ha-tip";
|
||||
import type { BackupConfig, BackupDay } from "../../../../../data/backup";
|
||||
import type {
|
||||
BackupConfig,
|
||||
BackupDay,
|
||||
Retention,
|
||||
} from "../../../../../data/backup";
|
||||
import {
|
||||
BACKUP_DAYS,
|
||||
BackupScheduleRecurrence,
|
||||
@ -29,76 +30,32 @@ import {
|
||||
import type { SupervisorUpdateConfig } from "../../../../../data/supervisor/update";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { documentationUrl } from "../../../../../util/documentation-url";
|
||||
import "./ha-backup-config-retention";
|
||||
|
||||
export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;
|
||||
|
||||
const MIN_VALUE = 1;
|
||||
const MAX_VALUE = 50;
|
||||
|
||||
enum RetentionPreset {
|
||||
COPIES_3 = "copies_3",
|
||||
FOREVER = "forever",
|
||||
CUSTOM = "custom",
|
||||
}
|
||||
|
||||
enum BackupScheduleTime {
|
||||
DEFAULT = "default",
|
||||
CUSTOM = "custom",
|
||||
}
|
||||
|
||||
interface RetentionData {
|
||||
type: "copies" | "days" | "forever";
|
||||
value: number;
|
||||
}
|
||||
|
||||
const RETENTION_PRESETS: Record<
|
||||
Exclude<RetentionPreset, RetentionPreset.CUSTOM>,
|
||||
RetentionData
|
||||
> = {
|
||||
copies_3: { type: "copies", value: 3 },
|
||||
forever: { type: "forever", value: 0 },
|
||||
};
|
||||
|
||||
const SCHEDULE_OPTIONS = [
|
||||
BackupScheduleRecurrence.NEVER,
|
||||
BackupScheduleRecurrence.DAILY,
|
||||
BackupScheduleRecurrence.CUSTOM_DAYS,
|
||||
] as const satisfies BackupScheduleRecurrence[];
|
||||
|
||||
const RETENTION_PRESETS_OPTIONS = [
|
||||
RetentionPreset.COPIES_3,
|
||||
RetentionPreset.FOREVER,
|
||||
RetentionPreset.CUSTOM,
|
||||
] as const satisfies RetentionPreset[];
|
||||
|
||||
const SCHEDULE_TIME_OPTIONS = [
|
||||
BackupScheduleTime.DEFAULT,
|
||||
BackupScheduleTime.CUSTOM,
|
||||
] as const satisfies BackupScheduleTime[];
|
||||
|
||||
const computeRetentionPreset = (
|
||||
data: RetentionData
|
||||
): RetentionPreset | undefined => {
|
||||
for (const [key, value] of Object.entries(RETENTION_PRESETS)) {
|
||||
if (
|
||||
value.type === data.type &&
|
||||
(value.type === RetentionPreset.FOREVER || value.value === data.value)
|
||||
) {
|
||||
return key as RetentionPreset;
|
||||
}
|
||||
}
|
||||
return RetentionPreset.CUSTOM;
|
||||
};
|
||||
|
||||
interface FormData {
|
||||
recurrence: BackupScheduleRecurrence;
|
||||
time_option: BackupScheduleTime;
|
||||
time?: string | null;
|
||||
days: BackupDay[];
|
||||
retention: {
|
||||
type: "copies" | "days" | "forever";
|
||||
value: number;
|
||||
};
|
||||
retention: Retention;
|
||||
}
|
||||
|
||||
const INITIAL_FORM_DATA: FormData = {
|
||||
@ -106,8 +63,7 @@ const INITIAL_FORM_DATA: FormData = {
|
||||
time_option: BackupScheduleTime.DEFAULT,
|
||||
days: [],
|
||||
retention: {
|
||||
type: "copies",
|
||||
value: 3,
|
||||
copies: 3,
|
||||
},
|
||||
};
|
||||
|
||||
@ -122,17 +78,6 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public supervisorUpdateConfig?: SupervisorUpdateConfig;
|
||||
|
||||
@state() private _retentionPreset?: RetentionPreset;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("value")) {
|
||||
if (this._retentionPreset !== RetentionPreset.CUSTOM) {
|
||||
const data = this._getData(this.value);
|
||||
this._retentionPreset = computeRetentionPreset(data.retention);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getData = memoizeOne((value?: BackupConfigSchedule): FormData => {
|
||||
if (!value) {
|
||||
return INITIAL_FORM_DATA;
|
||||
@ -150,15 +95,7 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
config.schedule.recurrence === BackupScheduleRecurrence.CUSTOM_DAYS
|
||||
? config.schedule.days
|
||||
: [],
|
||||
retention: {
|
||||
type:
|
||||
config.retention.days === null && config.retention.copies === null
|
||||
? "forever"
|
||||
: config.retention.days != null
|
||||
? "days"
|
||||
: "copies",
|
||||
value: config.retention.days ?? config.retention.copies ?? 3,
|
||||
},
|
||||
retention: config.retention,
|
||||
};
|
||||
});
|
||||
|
||||
@ -173,12 +110,7 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
? data.days
|
||||
: [],
|
||||
},
|
||||
retention:
|
||||
data.retention.type === "forever"
|
||||
? { days: null, copies: null }
|
||||
: data.retention.type === "days"
|
||||
? { days: data.retention.value, copies: null }
|
||||
: { copies: data.retention.value, days: null },
|
||||
retention: data.retention,
|
||||
};
|
||||
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
@ -377,81 +309,11 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(`ui.panel.config.backup.schedule.retention`)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.schedule.retention_description`
|
||||
)}
|
||||
</span>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@change=${this._retentionPresetChanged}
|
||||
.value=${this._retentionPreset ?? ""}
|
||||
>
|
||||
${RETENTION_PRESETS_OPTIONS.map(
|
||||
(option) => html`
|
||||
<ha-md-select-option .value=${option}>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.schedule.retention_presets.${option}`
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
`
|
||||
)}
|
||||
</ha-md-select>
|
||||
</ha-md-list-item>
|
||||
|
||||
${this._retentionPreset === RetentionPreset.CUSTOM
|
||||
? html`<ha-expansion-panel
|
||||
expanded
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.custom_retention"
|
||||
)}
|
||||
outlined
|
||||
>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.custom_retention_label"
|
||||
)}
|
||||
</span>
|
||||
<ha-md-textfield
|
||||
slot="end"
|
||||
@change=${this._retentionValueChanged}
|
||||
.value=${data.retention.value.toString()}
|
||||
id="value"
|
||||
type="number"
|
||||
.min=${MIN_VALUE.toString()}
|
||||
.max=${MAX_VALUE.toString()}
|
||||
step="1"
|
||||
>
|
||||
</ha-md-textfield>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@change=${this._retentionTypeChanged}
|
||||
.value=${data.retention.type}
|
||||
id="type"
|
||||
>
|
||||
<ha-md-select-option value="days">
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.retention_units.days"
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option value="copies">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.retention_units.copies"
|
||||
)}
|
||||
</ha-md-select-option>
|
||||
</ha-md-select>
|
||||
</ha-md-list-item></ha-expansion-panel
|
||||
> `
|
||||
: nothing}
|
||||
<ha-backup-config-retention
|
||||
.hass=${this.hass}
|
||||
.retention=${data.retention}
|
||||
@value-changed=${this._retentionChanged}
|
||||
></ha-backup-config-retention>
|
||||
<ha-tip .hass=${this.hass}
|
||||
>${this.hass.localize("ui.panel.config.backup.schedule.tip", {
|
||||
backup_create: html`<a
|
||||
@ -543,65 +405,18 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _retentionPresetChanged(ev) {
|
||||
private _retentionChanged(ev: CustomEvent<{ value: Retention }>) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget as HaMdSelect;
|
||||
let value = target.value as RetentionPreset;
|
||||
const retention = ev.detail.value;
|
||||
|
||||
// custom needs to have a type of days or copies, set it to default copies 3
|
||||
if (
|
||||
value === RetentionPreset.CUSTOM &&
|
||||
this._retentionPreset === RetentionPreset.FOREVER
|
||||
) {
|
||||
this._retentionPreset = value;
|
||||
value = RetentionPreset.COPIES_3;
|
||||
} else {
|
||||
this._retentionPreset = value;
|
||||
}
|
||||
|
||||
if (value !== RetentionPreset.CUSTOM) {
|
||||
const data = this._getData(this.value);
|
||||
const retention = RETENTION_PRESETS[value];
|
||||
// Ensure we have at least 1 in default value because user can't select 0
|
||||
if (value !== RetentionPreset.FOREVER) {
|
||||
retention.value = Math.max(retention.value, 1);
|
||||
}
|
||||
this._setData({
|
||||
|
||||
const newData = {
|
||||
...data,
|
||||
retention,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private _retentionValueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget as HaMdSelect;
|
||||
const value = parseInt(target.value);
|
||||
const clamped = clamp(value, MIN_VALUE, MAX_VALUE);
|
||||
const data = this._getData(this.value);
|
||||
target.value = clamped.toString();
|
||||
this._setData({
|
||||
...data,
|
||||
retention: {
|
||||
...data.retention,
|
||||
value: clamped,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _retentionTypeChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.currentTarget as HaMdSelect;
|
||||
const value = target.value as "copies" | "days";
|
||||
|
||||
const data = this._getData(this.value);
|
||||
this._setData({
|
||||
...data,
|
||||
retention: {
|
||||
...data.retention,
|
||||
type: value,
|
||||
},
|
||||
});
|
||||
this._setData(newData);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
@ -631,25 +446,7 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
width: 145px;
|
||||
}
|
||||
}
|
||||
ha-md-textfield#value {
|
||||
min-width: 70px;
|
||||
}
|
||||
ha-md-select#type {
|
||||
min-width: 100px;
|
||||
}
|
||||
@media all and (max-width: 450px) {
|
||||
ha-md-textfield#value {
|
||||
min-width: 60px;
|
||||
margin: 0 -8px;
|
||||
}
|
||||
ha-md-select#type {
|
||||
min-width: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
--expansion-panel-content-padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-tip {
|
||||
|
@ -1,32 +1,35 @@
|
||||
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 "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fade-in";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-switch";
|
||||
import type {
|
||||
BackupAgent,
|
||||
BackupAgentConfig,
|
||||
BackupConfig,
|
||||
Retention,
|
||||
} from "../../../data/backup";
|
||||
import {
|
||||
CLOUD_AGENT,
|
||||
computeBackupAgentName,
|
||||
fetchBackupAgentsInfo,
|
||||
isLocalAgent,
|
||||
updateBackupConfig,
|
||||
} from "../../../data/backup";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./components/ha-backup-data-picker";
|
||||
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "./components/config/ha-backup-config-retention";
|
||||
import "./components/ha-backup-data-picker";
|
||||
|
||||
@customElement("ha-config-backup-location")
|
||||
class HaConfigBackupDetails extends LitElement {
|
||||
@ -61,18 +64,21 @@ class HaConfigBackupDetails extends LitElement {
|
||||
|
||||
const encrypted = this._isEncryptionTurnedOn();
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup/settings"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${(this._agent &&
|
||||
const agentName =
|
||||
(this._agent &&
|
||||
computeBackupAgentName(
|
||||
this.hass.localize,
|
||||
this.agentId,
|
||||
this.agents
|
||||
)) ||
|
||||
this.hass.localize("ui.panel.config.backup.location.header")}
|
||||
this.hass.localize("ui.panel.config.backup.location.header");
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup/settings"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${agentName}
|
||||
>
|
||||
<div class="content">
|
||||
${this._error &&
|
||||
@ -96,14 +102,14 @@ class HaConfigBackupDetails extends LitElement {
|
||||
><ha-spinner></ha-spinner
|
||||
></ha-fade-in>`
|
||||
: html`
|
||||
${CLOUD_AGENT === this.agentId
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.location.configuration.title"
|
||||
)}
|
||||
</div>
|
||||
${CLOUD_AGENT === this.agentId
|
||||
? html`
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
@ -111,9 +117,21 @@ class HaConfigBackupDetails extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: this.config?.agents[this.agentId]
|
||||
? html`<ha-backup-config-retention
|
||||
location-specific
|
||||
.headline=${this.hass.localize(
|
||||
`ui.panel.config.backup.location.retention_for_${isLocalAgent(this.agentId) ? "this_system" : "location"}`,
|
||||
{ location: agentName }
|
||||
)}
|
||||
.hass=${this.hass}
|
||||
.retention=${this.config?.agents[this.agentId]
|
||||
?.retention}
|
||||
@value-changed=${this._retentionChanged}
|
||||
></ha-backup-config-retention>`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
@ -247,18 +265,37 @@ class HaConfigBackupDetails extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateAgentEncryption(value: boolean) {
|
||||
const agentsConfig = {
|
||||
...this.config?.agents,
|
||||
[this.agentId]: {
|
||||
...this.config?.agents[this.agentId],
|
||||
protected: value,
|
||||
},
|
||||
private async _updateAgentConfig(config: Partial<BackupAgentConfig>) {
|
||||
try {
|
||||
const agents = this.config?.agents || {};
|
||||
agents[this.agentId] = {
|
||||
...(agents[this.agentId] || {}),
|
||||
...config,
|
||||
};
|
||||
|
||||
await updateBackupConfig(this.hass, {
|
||||
agents: agentsConfig,
|
||||
agents,
|
||||
});
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
} catch (err: any) {
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.location.save_error",
|
||||
{ error: err.message }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _retentionChanged(ev: CustomEvent<{ value: Retention }>) {
|
||||
const retention = ev.detail.value;
|
||||
this._updateAgentConfig({
|
||||
retention,
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateAgentEncryption(value: boolean) {
|
||||
this._updateAgentConfig({
|
||||
protected: value,
|
||||
});
|
||||
}
|
||||
|
||||
private _turnOnEncryption() {
|
||||
@ -363,6 +400,10 @@ class HaConfigBackupDetails extends LitElement {
|
||||
ha-spinner {
|
||||
margin: 24px auto;
|
||||
}
|
||||
ha-backup-config-retention {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -2533,6 +2533,7 @@
|
||||
"custom_retention_label": "Keep only",
|
||||
"retention_description": "Based on the maximum number of backups or how many days they should be kept.",
|
||||
"retention_presets": {
|
||||
"global": "Use global settings",
|
||||
"copies_3": "3 backups",
|
||||
"forever": "Forever",
|
||||
"custom": "Custom"
|
||||
@ -2762,6 +2763,9 @@
|
||||
},
|
||||
"location": {
|
||||
"header": "Location",
|
||||
"save_error": "Error saving configuration: {error}",
|
||||
"retention_for_this_system": "Retention for this system",
|
||||
"retention_for_location": "Retention for {location}",
|
||||
"not_found": "Not found",
|
||||
"not_found_description": "Location matching ''{backupId}'' not found",
|
||||
"error": "Could not fetch location details",
|
||||
|
Loading…
x
Reference in New Issue
Block a user