mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
Add time option for backup schedule (#23757)
Co-authored-by: Wendelin <w@pe8.at>
This commit is contained in:
parent
e994e3565d
commit
7535d66373
@ -337,6 +337,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
}
|
}
|
||||||
.time-input-wrap {
|
.time-input-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -345,6 +346,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
width: 55px;
|
width: 55px;
|
||||||
|
flex-grow: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
--mdc-shape-small: 0;
|
--mdc-shape-small: 0;
|
||||||
--text-field-appearance: none;
|
--text-field-appearance: none;
|
||||||
|
@ -17,6 +17,7 @@ export class HaMdListItem extends MdListItem {
|
|||||||
}
|
}
|
||||||
md-item {
|
md-item {
|
||||||
overflow: var(--md-item-overflow, hidden);
|
overflow: var(--md-item-overflow, hidden);
|
||||||
|
align-items: var(--md-item-align-items, center);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -11,22 +11,33 @@ import type { HomeAssistant } from "../types";
|
|||||||
import { fileDownload } from "../util/file_download";
|
import { fileDownload } from "../util/file_download";
|
||||||
import { domainToName } from "./integration";
|
import { domainToName } from "./integration";
|
||||||
import type { FrontendLocaleData } from "./translation";
|
import type { FrontendLocaleData } from "./translation";
|
||||||
|
import checkValidDate from "../common/datetime/check_valid_date";
|
||||||
|
|
||||||
export const enum BackupScheduleState {
|
export const enum BackupScheduleRecurrence {
|
||||||
NEVER = "never",
|
NEVER = "never",
|
||||||
DAILY = "daily",
|
DAILY = "daily",
|
||||||
MONDAY = "mon",
|
CUSTOM_DAYS = "custom_days",
|
||||||
TUESDAY = "tue",
|
|
||||||
WEDNESDAY = "wed",
|
|
||||||
THURSDAY = "thu",
|
|
||||||
FRIDAY = "fri",
|
|
||||||
SATURDAY = "sat",
|
|
||||||
SUNDAY = "sun",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BackupDay = "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun";
|
||||||
|
|
||||||
|
export const BACKUP_DAYS: BackupDay[] = [
|
||||||
|
"mon",
|
||||||
|
"tue",
|
||||||
|
"wed",
|
||||||
|
"thu",
|
||||||
|
"fri",
|
||||||
|
"sat",
|
||||||
|
"sun",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sortWeekdays = (weekdays) =>
|
||||||
|
weekdays.sort((a, b) => BACKUP_DAYS.indexOf(a) - BACKUP_DAYS.indexOf(b));
|
||||||
|
|
||||||
export interface BackupConfig {
|
export interface BackupConfig {
|
||||||
last_attempted_automatic_backup: string | null;
|
last_attempted_automatic_backup: string | null;
|
||||||
last_completed_automatic_backup: string | null;
|
last_completed_automatic_backup: string | null;
|
||||||
|
next_automatic_backup: string | null;
|
||||||
create_backup: {
|
create_backup: {
|
||||||
agent_ids: string[];
|
agent_ids: string[];
|
||||||
include_addons: string[] | null;
|
include_addons: string[] | null;
|
||||||
@ -41,7 +52,9 @@ export interface BackupConfig {
|
|||||||
days?: number | null;
|
days?: number | null;
|
||||||
};
|
};
|
||||||
schedule: {
|
schedule: {
|
||||||
state: BackupScheduleState;
|
recurrence: BackupScheduleRecurrence;
|
||||||
|
time?: string | null;
|
||||||
|
days: BackupDay[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +72,11 @@ export interface BackupMutableConfig {
|
|||||||
copies?: number | null;
|
copies?: number | null;
|
||||||
days?: number | null;
|
days?: number | null;
|
||||||
};
|
};
|
||||||
schedule?: BackupScheduleState;
|
schedule?: {
|
||||||
|
recurrence: BackupScheduleRecurrence;
|
||||||
|
time?: string | null;
|
||||||
|
days?: BackupDay[] | null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackupAgent {
|
export interface BackupAgent {
|
||||||
@ -337,9 +354,34 @@ export const downloadEmergencyKit = (
|
|||||||
geneateEmergencyKitFileName(hass, appendFileName)
|
geneateEmergencyKitFileName(hass, appendFileName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const DEFAULT_OPTIMIZED_BACKUP_START_TIME = setMinutes(
|
||||||
|
setHours(new Date(), 4),
|
||||||
|
45
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DEFAULT_OPTIMIZED_BACKUP_END_TIME = setMinutes(
|
||||||
|
setHours(new Date(), 5),
|
||||||
|
45
|
||||||
|
);
|
||||||
|
|
||||||
export const getFormattedBackupTime = memoizeOne(
|
export const getFormattedBackupTime = memoizeOne(
|
||||||
(locale: FrontendLocaleData, config: HassConfig) => {
|
(
|
||||||
const date = setMinutes(setHours(new Date(), 4), 45);
|
locale: FrontendLocaleData,
|
||||||
return formatTime(date, locale, config);
|
config: HassConfig,
|
||||||
|
backupTime?: Date | string | null
|
||||||
|
) => {
|
||||||
|
if (checkValidDate(backupTime as Date)) {
|
||||||
|
return formatTime(backupTime as Date, locale, config);
|
||||||
|
}
|
||||||
|
if (typeof backupTime === "string" && backupTime) {
|
||||||
|
const splitted = backupTime.split(":");
|
||||||
|
const date = setMinutes(
|
||||||
|
setHours(new Date(), parseInt(splitted[0])),
|
||||||
|
parseInt(splitted[1])
|
||||||
|
);
|
||||||
|
return formatTime(date, locale, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${formatTime(DEFAULT_OPTIMIZED_BACKUP_START_TIME, locale, config)} - ${formatTime(DEFAULT_OPTIMIZED_BACKUP_END_TIME, locale, config)}`;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -12,12 +12,22 @@ import type { HaMdSelect } from "../../../../../components/ha-md-select";
|
|||||||
import "../../../../../components/ha-md-select-option";
|
import "../../../../../components/ha-md-select-option";
|
||||||
import "../../../../../components/ha-md-textfield";
|
import "../../../../../components/ha-md-textfield";
|
||||||
import "../../../../../components/ha-switch";
|
import "../../../../../components/ha-switch";
|
||||||
import type { BackupConfig } from "../../../../../data/backup";
|
import type { BackupConfig, BackupDay } from "../../../../../data/backup";
|
||||||
import {
|
import {
|
||||||
BackupScheduleState,
|
BACKUP_DAYS,
|
||||||
getFormattedBackupTime,
|
BackupScheduleRecurrence,
|
||||||
|
DEFAULT_OPTIMIZED_BACKUP_END_TIME,
|
||||||
|
DEFAULT_OPTIMIZED_BACKUP_START_TIME,
|
||||||
|
sortWeekdays,
|
||||||
} from "../../../../../data/backup";
|
} from "../../../../../data/backup";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
import "../../../../../components/ha-time-input";
|
||||||
|
import "../../../../../components/ha-tip";
|
||||||
|
import "../../../../../components/ha-expansion-panel";
|
||||||
|
import "../../../../../components/ha-checkbox";
|
||||||
|
import "../../../../../components/ha-formfield";
|
||||||
|
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||||
|
import { documentationUrl } from "../../../../../util/documentation-url";
|
||||||
|
|
||||||
export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;
|
export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;
|
||||||
|
|
||||||
@ -30,6 +40,11 @@ enum RetentionPreset {
|
|||||||
CUSTOM = "custom",
|
CUSTOM = "custom",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BackupScheduleTime {
|
||||||
|
DEFAULT = "default",
|
||||||
|
CUSTOM = "custom",
|
||||||
|
}
|
||||||
|
|
||||||
interface RetentionData {
|
interface RetentionData {
|
||||||
type: "copies" | "days";
|
type: "copies" | "days";
|
||||||
value: number;
|
value: number;
|
||||||
@ -44,15 +59,10 @@ const RETENTION_PRESETS: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SCHEDULE_OPTIONS = [
|
const SCHEDULE_OPTIONS = [
|
||||||
BackupScheduleState.DAILY,
|
BackupScheduleRecurrence.NEVER,
|
||||||
BackupScheduleState.MONDAY,
|
BackupScheduleRecurrence.DAILY,
|
||||||
BackupScheduleState.TUESDAY,
|
BackupScheduleRecurrence.CUSTOM_DAYS,
|
||||||
BackupScheduleState.WEDNESDAY,
|
] as const satisfies BackupScheduleRecurrence[];
|
||||||
BackupScheduleState.THURSDAY,
|
|
||||||
BackupScheduleState.FRIDAY,
|
|
||||||
BackupScheduleState.SATURDAY,
|
|
||||||
BackupScheduleState.SUNDAY,
|
|
||||||
] as const satisfies BackupScheduleState[];
|
|
||||||
|
|
||||||
const RETENTION_PRESETS_OPTIONS = [
|
const RETENTION_PRESETS_OPTIONS = [
|
||||||
RetentionPreset.COPIES_3,
|
RetentionPreset.COPIES_3,
|
||||||
@ -60,6 +70,11 @@ const RETENTION_PRESETS_OPTIONS = [
|
|||||||
RetentionPreset.CUSTOM,
|
RetentionPreset.CUSTOM,
|
||||||
] as const satisfies RetentionPreset[];
|
] as const satisfies RetentionPreset[];
|
||||||
|
|
||||||
|
const SCHEDULE_TIME_OPTIONS = [
|
||||||
|
BackupScheduleTime.DEFAULT,
|
||||||
|
BackupScheduleTime.CUSTOM,
|
||||||
|
] as const satisfies BackupScheduleTime[];
|
||||||
|
|
||||||
const computeRetentionPreset = (
|
const computeRetentionPreset = (
|
||||||
data: RetentionData
|
data: RetentionData
|
||||||
): RetentionPreset | undefined => {
|
): RetentionPreset | undefined => {
|
||||||
@ -72,8 +87,10 @@ const computeRetentionPreset = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
enabled: boolean;
|
recurrence: BackupScheduleRecurrence;
|
||||||
schedule: BackupScheduleState;
|
time_option: BackupScheduleTime;
|
||||||
|
time?: string | null;
|
||||||
|
days: BackupDay[];
|
||||||
retention: {
|
retention: {
|
||||||
type: "copies" | "days";
|
type: "copies" | "days";
|
||||||
value: number;
|
value: number;
|
||||||
@ -81,8 +98,9 @@ interface FormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_FORM_DATA: FormData = {
|
const INITIAL_FORM_DATA: FormData = {
|
||||||
enabled: false,
|
recurrence: BackupScheduleRecurrence.NEVER,
|
||||||
schedule: BackupScheduleState.NEVER,
|
time_option: BackupScheduleTime.DEFAULT,
|
||||||
|
days: [],
|
||||||
retention: {
|
retention: {
|
||||||
type: "copies",
|
type: "copies",
|
||||||
value: 3,
|
value: 3,
|
||||||
@ -114,8 +132,15 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
const config = value;
|
const config = value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: config.schedule.state !== BackupScheduleState.NEVER,
|
recurrence: config.schedule.recurrence,
|
||||||
schedule: config.schedule.state,
|
time_option: config.schedule.time
|
||||||
|
? BackupScheduleTime.CUSTOM
|
||||||
|
: BackupScheduleTime.DEFAULT,
|
||||||
|
time: config.schedule.time,
|
||||||
|
days:
|
||||||
|
config.schedule.recurrence === BackupScheduleRecurrence.CUSTOM_DAYS
|
||||||
|
? config.schedule.days
|
||||||
|
: [],
|
||||||
retention: {
|
retention: {
|
||||||
type: config.retention.days != null ? "days" : "copies",
|
type: config.retention.days != null ? "days" : "copies",
|
||||||
value: config.retention.days ?? config.retention.copies ?? 3,
|
value: config.retention.days ?? config.retention.copies ?? 3,
|
||||||
@ -125,8 +150,14 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
|
|
||||||
private _setData(data: FormData) {
|
private _setData(data: FormData) {
|
||||||
this.value = {
|
this.value = {
|
||||||
|
...this.value,
|
||||||
schedule: {
|
schedule: {
|
||||||
state: data.enabled ? data.schedule : BackupScheduleState.NEVER,
|
recurrence: data.recurrence,
|
||||||
|
time: data.time_option === BackupScheduleTime.CUSTOM ? data.time : null,
|
||||||
|
days:
|
||||||
|
data.recurrence === BackupScheduleRecurrence.CUSTOM_DAYS
|
||||||
|
? data.days
|
||||||
|
: [],
|
||||||
},
|
},
|
||||||
retention:
|
retention:
|
||||||
data.retention.type === "days"
|
data.retention.type === "days"
|
||||||
@ -140,49 +171,118 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
const data = this._getData(this.value);
|
const data = this._getData(this.value);
|
||||||
|
|
||||||
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">
|
<span slot="headline">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.backup.schedule.use_automatic_backups"
|
"ui.panel.config.backup.schedule.schedule"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span slot="supporting-text">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.backup.schedule.schedule_description"
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ha-switch
|
<ha-md-select
|
||||||
slot="end"
|
slot="end"
|
||||||
@change=${this._enabledChanged}
|
@change=${this._scheduleChanged}
|
||||||
.checked=${data.enabled}
|
.value=${data.recurrence}
|
||||||
></ha-switch>
|
>
|
||||||
|
${SCHEDULE_OPTIONS.map(
|
||||||
|
(option) => html`
|
||||||
|
<ha-md-select-option .value=${option}>
|
||||||
|
<div slot="headline">
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.backup.schedule.schedule_options.${option}`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-md-select-option>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-md-select>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
${data.enabled
|
${data.recurrence === BackupScheduleRecurrence.CUSTOM_DAYS
|
||||||
|
? html`<ha-expansion-panel
|
||||||
|
expanded
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.backup.schedule.custom_schedule"
|
||||||
|
)}
|
||||||
|
outlined
|
||||||
|
>
|
||||||
|
<ha-md-list-item class="days">
|
||||||
|
<span slot="headline">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.backup.schedule.backup_every"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<div slot="end">
|
||||||
|
${BACKUP_DAYS.map(
|
||||||
|
(day) => html`
|
||||||
|
<div>
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize(`ui.panel.config.backup.overview.settings.weekdays.${day}`)}
|
||||||
|
>
|
||||||
|
<ha-checkbox
|
||||||
|
@change=${this._daysChanged}
|
||||||
|
.checked=${data.days.includes(day)}
|
||||||
|
.value=${day}
|
||||||
|
>
|
||||||
|
</ha-checkbox>
|
||||||
|
</span>
|
||||||
|
</ha-formfield>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-md-list-item>
|
||||||
|
</ha-expansion-panel>`
|
||||||
|
: nothing}
|
||||||
|
${data.recurrence === BackupScheduleRecurrence.DAILY ||
|
||||||
|
(data.recurrence === BackupScheduleRecurrence.CUSTOM_DAYS &&
|
||||||
|
data.days.length > 0)
|
||||||
? html`
|
? html`
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">
|
<span slot="headline">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.backup.schedule.schedule"
|
"ui.panel.config.backup.schedule.time"
|
||||||
)}
|
)}</span
|
||||||
</span>
|
>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.backup.schedule.schedule_description"
|
"ui.panel.config.backup.schedule.schedule_time_description"
|
||||||
)}
|
)}
|
||||||
|
${data.time_option === BackupScheduleTime.DEFAULT
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.backup.schedule.schedule_time_optimal_description",
|
||||||
|
{
|
||||||
|
time_range_start: formatTime(
|
||||||
|
DEFAULT_OPTIMIZED_BACKUP_START_TIME,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
),
|
||||||
|
time_range_end: formatTime(
|
||||||
|
DEFAULT_OPTIMIZED_BACKUP_END_TIME,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: nothing}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ha-md-select
|
<ha-md-select
|
||||||
slot="end"
|
slot="end"
|
||||||
@change=${this._scheduleChanged}
|
@change=${this._scheduleTimeChanged}
|
||||||
.value=${data.schedule}
|
.value=${data.time_option}
|
||||||
>
|
>
|
||||||
${SCHEDULE_OPTIONS.map(
|
${SCHEDULE_TIME_OPTIONS.map(
|
||||||
(option) => html`
|
(option) => html`
|
||||||
<ha-md-select-option .value=${option}>
|
<ha-md-select-option .value=${option}>
|
||||||
<div slot="headline">
|
<div slot="headline">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.backup.schedule.schedule_options.${option}`,
|
`ui.panel.config.backup.schedule.time_options.${option}`
|
||||||
{ time }
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
@ -190,100 +290,197 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</ha-md-select>
|
</ha-md-select>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
${data.time_option === BackupScheduleTime.CUSTOM
|
||||||
<span slot="headline">
|
? html`<ha-expansion-panel
|
||||||
${this.hass.localize(
|
expanded
|
||||||
`ui.panel.config.backup.schedule.retention`
|
.header=${this.hass.localize(
|
||||||
)}
|
"ui.panel.config.backup.schedule.custom_time"
|
||||||
</span>
|
)}
|
||||||
<span slot="supporting-text">
|
outlined
|
||||||
${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-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-md-textfield
|
<span slot="headline">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.backup.schedule.custom_time_label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="supporting-text">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.backup.schedule.custom_time_description",
|
||||||
|
{
|
||||||
|
time: formatTime(
|
||||||
|
DEFAULT_OPTIMIZED_BACKUP_START_TIME,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<ha-time-input
|
||||||
slot="end"
|
slot="end"
|
||||||
@change=${this._retentionValueChanged}
|
@value-changed=${this._timeChanged}
|
||||||
.value=${data.retention.value}
|
.value=${data.time ?? undefined}
|
||||||
id="value"
|
.locale=${this.hass.locale}
|
||||||
type="number"
|
|
||||||
.min=${MIN_VALUE}
|
|
||||||
.max=${MAX_VALUE}
|
|
||||||
step="1"
|
|
||||||
>
|
>
|
||||||
</ha-md-textfield>
|
</ha-time-input>
|
||||||
<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-md-list-item>
|
||||||
`
|
</ha-expansion-panel>`
|
||||||
: nothing}
|
: nothing}
|
||||||
`
|
`
|
||||||
: nothing}
|
: 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-tip .hass=${this.hass}
|
||||||
|
>${this.hass.localize("ui.panel.config.backup.schedule.tip", {
|
||||||
|
backup_create: html`<a
|
||||||
|
href=${documentationUrl(
|
||||||
|
this.hass,
|
||||||
|
"/integrations/backup#example-backing-up-every-night-at-300-am"
|
||||||
|
)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>backup.create</a
|
||||||
|
>`,
|
||||||
|
})}</ha-tip
|
||||||
|
>
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _enabledChanged(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const target = ev.currentTarget as HaCheckbox;
|
|
||||||
const data = this._getData(this.value);
|
|
||||||
this._setData({
|
|
||||||
...data,
|
|
||||||
enabled: target.checked,
|
|
||||||
schedule: target.checked
|
|
||||||
? BackupScheduleState.DAILY
|
|
||||||
: BackupScheduleState.NEVER,
|
|
||||||
});
|
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _scheduleChanged(ev) {
|
private _scheduleChanged(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const target = ev.currentTarget as HaMdSelect;
|
const target = ev.currentTarget as HaMdSelect;
|
||||||
const data = this._getData(this.value);
|
const data = this._getData(this.value);
|
||||||
|
let days = [...data.days];
|
||||||
|
|
||||||
|
if (
|
||||||
|
target.value === BackupScheduleRecurrence.CUSTOM_DAYS &&
|
||||||
|
data.days.length === 0
|
||||||
|
) {
|
||||||
|
days = [...BACKUP_DAYS];
|
||||||
|
}
|
||||||
|
|
||||||
this._setData({
|
this._setData({
|
||||||
...data,
|
...data,
|
||||||
schedule: target.value as BackupScheduleState,
|
recurrence: target.value as BackupScheduleRecurrence,
|
||||||
|
days,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _scheduleTimeChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const target = ev.currentTarget as HaMdSelect;
|
||||||
|
const data = this._getData(this.value);
|
||||||
|
this._setData({
|
||||||
|
...data,
|
||||||
|
time_option: target.value as BackupScheduleTime,
|
||||||
|
time: target.value === BackupScheduleTime.CUSTOM ? "04:45:00" : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _timeChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const data = this._getData(this.value);
|
||||||
|
|
||||||
|
this._setData({
|
||||||
|
...data,
|
||||||
|
time: ev.detail.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _daysChanged(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
const target = ev.currentTarget as HaCheckbox;
|
||||||
|
const value = target.value as BackupDay;
|
||||||
|
const data = this._getData(this.value);
|
||||||
|
const days = [...data.days];
|
||||||
|
|
||||||
|
if (target.checked && !data.days.includes(value)) {
|
||||||
|
days.push(value);
|
||||||
|
} else if (!target.checked && data.days.includes(value)) {
|
||||||
|
days.splice(days.indexOf(value), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortWeekdays(days);
|
||||||
|
|
||||||
|
this._setData({
|
||||||
|
...data,
|
||||||
|
days,
|
||||||
});
|
});
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _retentionPresetChanged(ev) {
|
private _retentionPresetChanged(ev) {
|
||||||
@ -304,8 +501,6 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
retention: RETENTION_PRESETS[value],
|
retention: RETENTION_PRESETS[value],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _retentionValueChanged(ev) {
|
private _retentionValueChanged(ev) {
|
||||||
@ -321,8 +516,6 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
value: clamped,
|
value: clamped,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _retentionTypeChanged(ev) {
|
private _retentionTypeChanged(ev) {
|
||||||
@ -338,8 +531,6 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
type: value,
|
type: value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@ -351,11 +542,13 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
ha-md-list-item {
|
ha-md-list-item {
|
||||||
--md-item-overflow: visible;
|
--md-item-overflow: visible;
|
||||||
}
|
}
|
||||||
ha-md-select {
|
ha-md-select,
|
||||||
|
ha-time-input {
|
||||||
min-width: 210px;
|
min-width: 210px;
|
||||||
}
|
}
|
||||||
@media all and (max-width: 450px) {
|
@media all and (max-width: 450px) {
|
||||||
ha-md-select {
|
ha-md-select,
|
||||||
|
ha-time-input {
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,6 +558,21 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
ha-md-select#type {
|
ha-md-select#type {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
ha-expansion-panel {
|
||||||
|
--expansion-panel-summary-padding: 0 16px;
|
||||||
|
--expansion-panel-content-padding: 0 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
ha-tip {
|
||||||
|
text-align: unset;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
ha-md-list-item.days {
|
||||||
|
--md-item-align-items: flex-start;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import "../../../../../components/ha-md-list-item";
|
|||||||
import "../../../../../components/ha-svg-icon";
|
import "../../../../../components/ha-svg-icon";
|
||||||
import type { BackupConfig } from "../../../../../data/backup";
|
import type { BackupConfig } from "../../../../../data/backup";
|
||||||
import {
|
import {
|
||||||
BackupScheduleState,
|
BackupScheduleRecurrence,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
getFormattedBackupTime,
|
getFormattedBackupTime,
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
@ -31,24 +31,87 @@ class HaBackupBackupsSummary extends LitElement {
|
|||||||
|
|
||||||
private _scheduleDescription(config: BackupConfig): string {
|
private _scheduleDescription(config: BackupConfig): string {
|
||||||
const { copies, days } = config.retention;
|
const { copies, days } = config.retention;
|
||||||
const { state: schedule } = config.schedule;
|
const { recurrence } = config.schedule;
|
||||||
|
|
||||||
if (schedule === BackupScheduleState.NEVER) {
|
if (recurrence === BackupScheduleRecurrence.NEVER) {
|
||||||
return this.hass.localize(
|
return this.hass.localize(
|
||||||
"ui.panel.config.backup.overview.settings.schedule_never"
|
"ui.panel.config.backup.overview.settings.schedule_never"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
const time: string | undefined | null =
|
||||||
|
this.config.schedule.time &&
|
||||||
|
getFormattedBackupTime(
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config,
|
||||||
|
this.config.schedule.time
|
||||||
|
);
|
||||||
|
|
||||||
const scheduleText = this.hass.localize(
|
let scheduleText = this.hass.localize(
|
||||||
`ui.panel.config.backup.overview.settings.schedule_${schedule}`,
|
"ui.panel.config.backup.overview.settings.schedule_never"
|
||||||
{ time }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const configDays = this.config.schedule.days;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.config.schedule.recurrence === BackupScheduleRecurrence.DAILY ||
|
||||||
|
(this.config.schedule.recurrence ===
|
||||||
|
BackupScheduleRecurrence.CUSTOM_DAYS &&
|
||||||
|
configDays.length === 7)
|
||||||
|
) {
|
||||||
|
scheduleText = this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.settings.schedule_${!this.config.schedule.time ? "optimized_" : ""}daily`,
|
||||||
|
{
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
this.config.schedule.recurrence ===
|
||||||
|
BackupScheduleRecurrence.CUSTOM_DAYS &&
|
||||||
|
configDays.length !== 0
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
configDays.length === 2 &&
|
||||||
|
configDays.includes("sat") &&
|
||||||
|
configDays.includes("sun")
|
||||||
|
) {
|
||||||
|
scheduleText = this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.settings.schedule_${!this.config.schedule.time ? "optimized_" : ""}weekend`,
|
||||||
|
{
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
configDays.length === 5 &&
|
||||||
|
!configDays.includes("sat") &&
|
||||||
|
!configDays.includes("sun")
|
||||||
|
) {
|
||||||
|
scheduleText = this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.settings.schedule_${!this.config.schedule.time ? "optimized_" : ""}weekdays`,
|
||||||
|
{
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
scheduleText = this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.settings.schedule_${!this.config.schedule.time ? "optimized_" : ""}days`,
|
||||||
|
{
|
||||||
|
count: configDays.length,
|
||||||
|
days: configDays
|
||||||
|
.map((dayCode) =>
|
||||||
|
this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.settings.${configDays.length > 2 ? "short_weekdays" : "weekdays"}.${dayCode}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.join(", "),
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let copiesText = this.hass.localize(
|
let copiesText = this.hass.localize(
|
||||||
`ui.panel.config.backup.overview.settings.schedule_copies_all`,
|
`ui.panel.config.backup.overview.settings.schedule_copies_all`
|
||||||
{ time }
|
|
||||||
);
|
);
|
||||||
if (copies) {
|
if (copies) {
|
||||||
copiesText = this.hass.localize(
|
copiesText = this.hass.localize(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
||||||
import { addHours, differenceInDays } from "date-fns";
|
import { addHours, differenceInDays, isToday, isTomorrow } from "date-fns";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
||||||
@ -12,12 +12,13 @@ import "../../../../../components/ha-md-list-item";
|
|||||||
import "../../../../../components/ha-svg-icon";
|
import "../../../../../components/ha-svg-icon";
|
||||||
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
||||||
import {
|
import {
|
||||||
BackupScheduleState,
|
BackupScheduleRecurrence,
|
||||||
getFormattedBackupTime,
|
getFormattedBackupTime,
|
||||||
} from "../../../../../data/backup";
|
} from "../../../../../data/backup";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-backup-summary-card";
|
import "../ha-backup-summary-card";
|
||||||
|
import { formatDateWeekday } from "../../../../../common/datetime/format_date";
|
||||||
|
|
||||||
const OVERDUE_MARGIN_HOURS = 3;
|
const OVERDUE_MARGIN_HOURS = 3;
|
||||||
|
|
||||||
@ -76,16 +77,6 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
const lastBackup = this._lastBackup(this.backups);
|
const lastBackup = this._lastBackup(this.backups);
|
||||||
|
|
||||||
const backupTime = getFormattedBackupTime(
|
|
||||||
this.hass.locale,
|
|
||||||
this.hass.config
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextBackupDescription = this.hass.localize(
|
|
||||||
`ui.panel.config.backup.overview.summary.next_backup_description.${this.config.schedule.state}`,
|
|
||||||
{ time: backupTime }
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastAttemptDate = this.config.last_attempted_automatic_backup
|
const lastAttemptDate = this.config.last_attempted_automatic_backup
|
||||||
? new Date(this.config.last_attempted_automatic_backup)
|
? new Date(this.config.last_attempted_automatic_backup)
|
||||||
: new Date(0);
|
: new Date(0);
|
||||||
@ -94,6 +85,46 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
? new Date(this.config.last_completed_automatic_backup)
|
? new Date(this.config.last_completed_automatic_backup)
|
||||||
: new Date(0);
|
: new Date(0);
|
||||||
|
|
||||||
|
const nextAutomaticDate = this.config.next_automatic_backup
|
||||||
|
? new Date(this.config.next_automatic_backup)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const backupTime = getFormattedBackupTime(
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config,
|
||||||
|
nextAutomaticDate || this.config.schedule.time
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextBackupDescription =
|
||||||
|
this.config.schedule.recurrence === BackupScheduleRecurrence.NEVER ||
|
||||||
|
(this.config.schedule.recurrence ===
|
||||||
|
BackupScheduleRecurrence.CUSTOM_DAYS &&
|
||||||
|
this.config.schedule.days.length === 0)
|
||||||
|
? this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.summary.no_automatic_backup`
|
||||||
|
)
|
||||||
|
: nextAutomaticDate
|
||||||
|
? this.hass.localize(
|
||||||
|
`ui.panel.config.backup.overview.summary.next_automatic_backup`,
|
||||||
|
{
|
||||||
|
day: isTomorrow(nextAutomaticDate)
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.backup.overview.summary.tomorrow"
|
||||||
|
)
|
||||||
|
: isToday(nextAutomaticDate)
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.backup.overview.summary.today"
|
||||||
|
)
|
||||||
|
: formatDateWeekday(
|
||||||
|
nextAutomaticDate,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
),
|
||||||
|
time: backupTime,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: "";
|
||||||
|
|
||||||
// If last attempt is after last completed backup, show error
|
// If last attempt is after last completed backup, show error
|
||||||
if (lastAttemptDate > lastCompletedDate) {
|
if (lastAttemptDate > lastCompletedDate) {
|
||||||
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||||
@ -122,25 +153,32 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
${lastUploadedBackup || nextBackupDescription
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
? html`
|
||||||
<span slot="headline">
|
<ha-md-list-item>
|
||||||
${lastUploadedBackup
|
<ha-svg-icon
|
||||||
? this.hass.localize(
|
slot="start"
|
||||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
.path=${mdiCalendar}
|
||||||
{
|
></ha-svg-icon>
|
||||||
relative_time: relativeTime(
|
<span slot="headline">
|
||||||
new Date(lastUploadedBackup.date),
|
${lastUploadedBackup
|
||||||
this.hass.locale,
|
? this.hass.localize(
|
||||||
now,
|
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||||
true
|
{
|
||||||
),
|
relative_time: relativeTime(
|
||||||
count: lastUploadedBackup.agent_ids?.length ?? 0,
|
new Date(lastUploadedBackup.date),
|
||||||
}
|
this.hass.locale,
|
||||||
)
|
now,
|
||||||
: nextBackupDescription}
|
true
|
||||||
</span>
|
),
|
||||||
</ha-md-list-item>
|
count: lastUploadedBackup.agent_ids?.length ?? 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: nextBackupDescription}
|
||||||
|
</span>
|
||||||
|
</ha-md-list-item>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
@ -164,10 +202,7 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
${this._renderNextBackupDescription(nextBackupDescription)}
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
|
||||||
<span slot="headline">${nextBackupDescription}</span>
|
|
||||||
</ha-md-list-item>
|
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
@ -203,25 +238,28 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
${lastUploadedBackup || nextBackupDescription
|
||||||
<span slot="headline">
|
? html` <ha-md-list-item>
|
||||||
${lastUploadedBackup
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
? this.hass.localize(
|
<span slot="headline">
|
||||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
${lastUploadedBackup
|
||||||
{
|
? this.hass.localize(
|
||||||
relative_time: relativeTime(
|
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||||
new Date(lastUploadedBackup.date),
|
{
|
||||||
this.hass.locale,
|
relative_time: relativeTime(
|
||||||
now,
|
new Date(lastUploadedBackup.date),
|
||||||
true
|
this.hass.locale,
|
||||||
),
|
now,
|
||||||
count: lastUploadedBackup.agent_ids?.length ?? 0,
|
true
|
||||||
}
|
),
|
||||||
)
|
count: lastUploadedBackup.agent_ids?.length ?? 0,
|
||||||
: nextBackupDescription}
|
}
|
||||||
</span>
|
)
|
||||||
</ha-md-list-item>
|
: nextBackupDescription}
|
||||||
|
</span>
|
||||||
|
</ha-md-list-item>`
|
||||||
|
: nothing}
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
@ -248,53 +286,37 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
const isOverdue =
|
const isOverdue =
|
||||||
(numberOfDays >= 1 &&
|
(numberOfDays >= 1 &&
|
||||||
this.config.schedule.state === BackupScheduleState.DAILY) ||
|
this.config.schedule.recurrence === BackupScheduleRecurrence.DAILY) ||
|
||||||
numberOfDays >= 7;
|
numberOfDays >= 7;
|
||||||
|
|
||||||
if (isOverdue) {
|
|
||||||
return html`
|
|
||||||
<ha-backup-summary-card
|
|
||||||
.heading=${this.hass.localize(
|
|
||||||
"ui.panel.config.backup.overview.summary.backup_too_old_heading",
|
|
||||||
{ count: numberOfDays }
|
|
||||||
)}
|
|
||||||
status="warning"
|
|
||||||
>
|
|
||||||
<ha-md-list>
|
|
||||||
<ha-md-list-item>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
|
||||||
<span slot="headline">${lastSuccessfulBackupDescription}</span>
|
|
||||||
</ha-md-list-item>
|
|
||||||
<ha-md-list-item>
|
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
|
||||||
<span slot="headline">${nextBackupDescription}</span>
|
|
||||||
</ha-md-list-item>
|
|
||||||
</ha-md-list>
|
|
||||||
</ha-backup-summary-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-summary-card
|
||||||
.heading=${this.hass.localize(
|
.heading=${this.hass.localize(
|
||||||
"ui.panel.config.backup.overview.summary.backup_success_heading"
|
`ui.panel.config.backup.overview.summary.${isOverdue ? "backup_too_old_heading" : "backup_success_heading"}`,
|
||||||
|
{ count: numberOfDays }
|
||||||
)}
|
)}
|
||||||
status="success"
|
.status=${isOverdue ? "warning" : "success"}
|
||||||
>
|
>
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
<span slot="headline">${lastSuccessfulBackupDescription}</span>
|
<span slot="headline">${lastSuccessfulBackupDescription}</span>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
${this._renderNextBackupDescription(nextBackupDescription)}
|
||||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
|
||||||
<span slot="headline">${nextBackupDescription}</span>
|
|
||||||
</ha-md-list-item>
|
|
||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderNextBackupDescription(nextBackupDescription: string) {
|
||||||
|
return nextBackupDescription
|
||||||
|
? html` <ha-md-list-item>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
<span slot="headline">${nextBackupDescription}</span>
|
||||||
|
</ha-md-list-item>`
|
||||||
|
: nothing;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -21,7 +21,7 @@ import type {
|
|||||||
BackupMutableConfig,
|
BackupMutableConfig,
|
||||||
} from "../../../../data/backup";
|
} from "../../../../data/backup";
|
||||||
import {
|
import {
|
||||||
BackupScheduleState,
|
BackupScheduleRecurrence,
|
||||||
CLOUD_AGENT,
|
CLOUD_AGENT,
|
||||||
CORE_LOCAL_AGENT,
|
CORE_LOCAL_AGENT,
|
||||||
downloadEmergencyKit,
|
downloadEmergencyKit,
|
||||||
@ -68,10 +68,13 @@ const RECOMMENDED_CONFIG: BackupConfig = {
|
|||||||
days: null,
|
days: null,
|
||||||
},
|
},
|
||||||
schedule: {
|
schedule: {
|
||||||
state: BackupScheduleState.DAILY,
|
recurrence: BackupScheduleRecurrence.DAILY,
|
||||||
|
time: null,
|
||||||
|
days: [],
|
||||||
},
|
},
|
||||||
last_attempted_automatic_backup: null,
|
last_attempted_automatic_backup: null,
|
||||||
last_completed_automatic_backup: null,
|
last_completed_automatic_backup: null,
|
||||||
|
next_automatic_backup: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-dialog-backup-onboarding")
|
@customElement("ha-dialog-backup-onboarding")
|
||||||
@ -145,7 +148,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
include_database: this._config.create_backup.include_database,
|
include_database: this._config.create_backup.include_database,
|
||||||
agent_ids: this._config.create_backup.agent_ids,
|
agent_ids: this._config.create_backup.agent_ids,
|
||||||
},
|
},
|
||||||
schedule: this._config.schedule.state,
|
schedule: this._config.schedule,
|
||||||
retention: this._config.retention,
|
retention: this._config.retention,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -308,7 +308,7 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
password: this._config!.create_backup.password,
|
password: this._config!.create_backup.password,
|
||||||
},
|
},
|
||||||
retention: this._config!.retention,
|
retention: this._config!.retention,
|
||||||
schedule: this._config!.schedule.state,
|
schedule: this._config!.schedule,
|
||||||
});
|
});
|
||||||
fireEvent(this, "ha-refresh-backup-config");
|
fireEvent(this, "ha-refresh-backup-config");
|
||||||
}
|
}
|
||||||
|
@ -2411,20 +2411,29 @@
|
|||||||
"addons": "Add-ons"
|
"addons": "Add-ons"
|
||||||
},
|
},
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"use_automatic_backups": "Use automatic backups",
|
|
||||||
"schedule": "Schedule",
|
"schedule": "Schedule",
|
||||||
|
"backup_every": "Backup every",
|
||||||
|
"custom_schedule": "Custom schedule",
|
||||||
|
"time": "Time",
|
||||||
|
"custom_time": "Custom time",
|
||||||
|
"custom_time_label": "Backup at",
|
||||||
|
"custom_time_description": "The optimal time is after Home Assistant finished cleaning up the database. This is done at {time}.",
|
||||||
"schedule_description": "How often you want to create a backup.",
|
"schedule_description": "How often you want to create a backup.",
|
||||||
|
"schedule_time_description": "When the backup creation starts.",
|
||||||
|
"schedule_time_optimal_description": "By default Home Assistant picks the optimal time between {time_range_start} and {time_range_end}.",
|
||||||
|
"tip": "You can create your own custom backup automation with the {backup_create} action",
|
||||||
"schedule_options": {
|
"schedule_options": {
|
||||||
"daily": "Daily at {time}",
|
"never": "Never",
|
||||||
"mon": "Monday at {time}",
|
"daily": "Daily",
|
||||||
"tue": "Tuesday at {time}",
|
"custom_days": "Custom days"
|
||||||
"wed": "Wednesday at {time}",
|
},
|
||||||
"thu": "Thursday at {time}",
|
"time_options": {
|
||||||
"fri": "Friday at {time}",
|
"default": "System optimal",
|
||||||
"sat": "Saturday at {time}",
|
"custom": "Custom"
|
||||||
"sun": "Sunday at {time}"
|
|
||||||
},
|
},
|
||||||
"retention": "Retention",
|
"retention": "Retention",
|
||||||
|
"custom_retention": "Custom retention",
|
||||||
|
"custom_retention_label": "Clean up every",
|
||||||
"retention_description": "Based on the maximum number of backups or how many days they should be kept.",
|
"retention_description": "Based on the maximum number of backups or how many days they should be kept.",
|
||||||
"retention_presets": {
|
"retention_presets": {
|
||||||
"copies_3": "3 backups",
|
"copies_3": "3 backups",
|
||||||
@ -2509,17 +2518,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
"next_backup_description": {
|
"no_automatic_backup": "No automatic backups scheduled",
|
||||||
"daily": "Next automatic backup tomorrow at {time}",
|
"next_automatic_backup": "Next automatic backup {day} at {time}",
|
||||||
"mon": "Next automatic backup next Monday at {time}",
|
"today": "today",
|
||||||
"tue": "Next automatic backup next Tuesday at {time}",
|
"tomorrow": "tomorrow",
|
||||||
"wed": "Next automatic backup next Wednesday at {time}",
|
|
||||||
"thu": "Next automatic backup next Thursday at {time}",
|
|
||||||
"fri": "Next automatic backup next Friday at {time}",
|
|
||||||
"sat": "Next automatic backup next Saturday at {time}",
|
|
||||||
"sun": "Next automatic backup next Sunday at {time}",
|
|
||||||
"never": "No automatic backups scheduled"
|
|
||||||
},
|
|
||||||
"loading": "Loading backups...",
|
"loading": "Loading backups...",
|
||||||
"last_backup_failed_heading": "Last automatic backup failed",
|
"last_backup_failed_heading": "Last automatic backup failed",
|
||||||
"last_backup_failed_description": "The last automatic backup triggered {relative_time} wasn't successful.",
|
"last_backup_failed_description": "The last automatic backup triggered {relative_time} wasn't successful.",
|
||||||
@ -2545,13 +2547,13 @@
|
|||||||
"schedule_copies_backups": "and keep {count} {count, plural,\n one {backup}\n other {backups}\n}",
|
"schedule_copies_backups": "and keep {count} {count, plural,\n one {backup}\n other {backups}\n}",
|
||||||
"schedule_copies_days": "and keep {count} {count, plural,\n one {day}\n other {days}\n}",
|
"schedule_copies_days": "and keep {count} {count, plural,\n one {day}\n other {days}\n}",
|
||||||
"schedule_daily": "Daily at {time}",
|
"schedule_daily": "Daily at {time}",
|
||||||
"schedule_mon": "Weekly on Mondays at {time}",
|
"schedule_days": "Every {days} at {time}",
|
||||||
"schedule_tue": "Weekly on Tuesdays at {time}",
|
"schedule_weekdays": "Every weekday at {time}",
|
||||||
"schedule_wed": "Weekly on Wednesdays at {time}",
|
"schedule_optimized_weekdays": "Every weekday",
|
||||||
"schedule_thu": "Weekly on Thursdays at {time}",
|
"schedule_weekend": "Every weekends at {time}",
|
||||||
"schedule_fri": "Weekly on Fridays at {time}",
|
"schedule_optimized_weekend": "Every weekends",
|
||||||
"schedule_sat": "Weekly on Saturdays at {time}",
|
"schedule_optimized_daily": "Daily",
|
||||||
"schedule_sun": "Weekly on Sundays at {time}",
|
"schedule_optimized_days": "Every {days}",
|
||||||
"schedule_never": "Automatic backups are not scheduled",
|
"schedule_never": "Automatic backups are not scheduled",
|
||||||
"data": "Home Assistant data that is included",
|
"data": "Home Assistant data that is included",
|
||||||
"data_settings_history": "Settings and history",
|
"data_settings_history": "Settings and history",
|
||||||
@ -2564,7 +2566,26 @@
|
|||||||
"locations_one": "Store in {name}",
|
"locations_one": "Store in {name}",
|
||||||
"locations_many": "Store in {count} off-site {count, plural,\n one {location}\n other {locations}\n}",
|
"locations_many": "Store in {count} off-site {count, plural,\n one {location}\n other {locations}\n}",
|
||||||
"locations_local_only": "Local backup only",
|
"locations_local_only": "Local backup only",
|
||||||
"locations_none": "No locations configured"
|
"locations_none": "No locations configured",
|
||||||
|
"system_optimal_time": "system optimal time",
|
||||||
|
"weekdays": {
|
||||||
|
"mon": "[%key:ui::weekdays::monday%]",
|
||||||
|
"tue": "[%key:ui::weekdays::tuesday%]",
|
||||||
|
"wed": "[%key:ui::weekdays::wednesday%]",
|
||||||
|
"thu": "[%key:ui::weekdays::thursday%]",
|
||||||
|
"fri": "[%key:ui::weekdays::friday%]",
|
||||||
|
"sat": "[%key:ui::weekdays::saturday%]",
|
||||||
|
"sun": "[%key:ui::weekdays::sunday%]"
|
||||||
|
},
|
||||||
|
"short_weekdays": {
|
||||||
|
"mon": "Mo",
|
||||||
|
"tue": "Tu",
|
||||||
|
"wed": "We",
|
||||||
|
"thu": "Th",
|
||||||
|
"fri": "Fr",
|
||||||
|
"sat": "Sa",
|
||||||
|
"sun": "So"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backups": {
|
"backups": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user