mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 23:06:40 +00:00
Merge branch 'rc'
This commit is contained in:
commit
3ffbd435e0
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250106.0"
|
||||
version = "20250109.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -83,11 +83,13 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("scroll", this._handleScroll, true);
|
||||
this._releaseCanvas();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("scroll", this._handleScroll, true);
|
||||
if (this.hasUpdated) {
|
||||
this._releaseCanvas();
|
||||
this._setupChart();
|
||||
@ -561,6 +563,10 @@ export class HaChartBase extends LitElement {
|
||||
this.chart?.resetZoom();
|
||||
}
|
||||
|
||||
private _handleScroll = () => {
|
||||
this._tooltip = undefined;
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@ -75,8 +75,10 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
direction: ltr;
|
||||
}
|
||||
mwc-button {
|
||||
flex: 1;
|
||||
--mdc-shape-small: 0;
|
||||
--mdc-button-outline-width: 1px 0 1px 1px;
|
||||
--mdc-button-outline-color: var(--primary-color);
|
||||
}
|
||||
ha-icon-button {
|
||||
border: 1px solid var(--primary-color);
|
||||
@ -94,7 +96,7 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background-color: currentColor;
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
@ -104,12 +106,22 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
}
|
||||
ha-icon-button[active]::before,
|
||||
mwc-button[active]::before {
|
||||
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
||||
opacity: 1;
|
||||
}
|
||||
ha-icon-button[active] {
|
||||
--icon-primary-color: var(--text-primary-color);
|
||||
}
|
||||
mwc-button[active] {
|
||||
--mdc-theme-primary: var(--text-primary-color);
|
||||
}
|
||||
ha-icon-button:first-child,
|
||||
mwc-button:first-child {
|
||||
--mdc-shape-small: 4px 0 0 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
mwc-button:first-child::before {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
ha-icon-button:last-child,
|
||||
mwc-button:last-child {
|
||||
@ -118,6 +130,9 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
--mdc-shape-small: 0 4px 4px 0;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
mwc-button:last-child::before {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
ha-icon-button:only-child,
|
||||
mwc-button:only-child {
|
||||
--mdc-shape-small: 4px;
|
||||
|
@ -87,6 +87,16 @@ export class HaButtonToggleSelector extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
@media all and (max-width: 600px) {
|
||||
ha-button-toggle-group {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import "../ha-state-icon";
|
||||
class HaEntityMarker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "entity-id" }) public entityId?: string;
|
||||
@property({ attribute: "entity-id", reflect: true }) public entityId?: string;
|
||||
|
||||
@property({ attribute: "entity-name" }) public entityName?: string;
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { setHours, setMinutes } from "date-fns";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { formatTime } from "../common/datetime/format_time";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { domainToName } from "./integration";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDateTimeNumeric,
|
||||
} from "../common/datetime/format_date_time";
|
||||
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 { domainToName } from "./integration";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
|
||||
export const enum BackupScheduleState {
|
||||
NEVER = "never",
|
||||
@ -217,10 +217,16 @@ export const uploadBackup = async (
|
||||
};
|
||||
|
||||
export const getPreferredAgentForDownload = (agents: string[]) => {
|
||||
const localAgents = agents.filter(
|
||||
(agent) => agent.split(".")[0] === "backup"
|
||||
);
|
||||
return localAgents[0] || agents[0];
|
||||
const localAgent = agents.find(isLocalAgent);
|
||||
if (localAgent) {
|
||||
return localAgent;
|
||||
}
|
||||
const networkMountAgent = agents.find(isNetworkMountAgent);
|
||||
if (networkMountAgent) {
|
||||
return networkMountAgent;
|
||||
}
|
||||
|
||||
return agents[0];
|
||||
};
|
||||
|
||||
export const CORE_LOCAL_AGENT = "backup.local";
|
||||
@ -241,7 +247,7 @@ export const computeBackupAgentName = (
|
||||
agentIds?: string[]
|
||||
) => {
|
||||
if (isLocalAgent(agentId)) {
|
||||
return "This system";
|
||||
return localize("ui.panel.config.backup.agents.local_agent");
|
||||
}
|
||||
const [domain, name] = agentId.split(".");
|
||||
|
||||
@ -298,23 +304,22 @@ export const generateEmergencyKit = (
|
||||
encryptionKey: string
|
||||
) =>
|
||||
"data:text/plain;charset=utf-8," +
|
||||
encodeURIComponent(`Home Assistant Backup Emergency Kit
|
||||
encodeURIComponent(`${hass.localize("ui.panel.config.backup.emergency_kit_file.title")}
|
||||
|
||||
This emergency kit contains your backup encryption key. You need this key
|
||||
to be able to restore your Home Assistant backups.
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.description")}
|
||||
|
||||
Date: ${formatDateTime(new Date(), hass.locale, hass.config)}
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.date")} ${formatDateTime(new Date(), hass.locale, hass.config)}
|
||||
|
||||
Instance:
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.instance")}
|
||||
${hass.config.location_name}
|
||||
|
||||
URL:
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.url")}
|
||||
${hass.auth.data.hassUrl}
|
||||
|
||||
Encryption key:
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.encryption_key")}
|
||||
${encryptionKey}
|
||||
|
||||
For more information visit: https://www.home-assistant.io/more-info/backup-emergency-kit`);
|
||||
${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: "https://www.home-assistant.io/more-info/backup-emergency-kit" })}`);
|
||||
|
||||
export const geneateEmergencyKitFileName = (
|
||||
hass: HomeAssistant,
|
||||
|
@ -323,6 +323,14 @@ class DialogAutomationRename extends LitElement implements HassDialog {
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0 24px 24px 24px;
|
||||
}
|
||||
|
||||
@media all and (min-width: 500px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: min(500px, 95vw);
|
||||
--mdc-dialog-max-width: min(500px, 95vw);
|
||||
}
|
||||
}
|
||||
|
||||
ha-textfield,
|
||||
ha-textarea,
|
||||
ha-icon-picker,
|
||||
|
@ -52,12 +52,18 @@ class HaBackupConfigAgents extends LitElement {
|
||||
private _description(agentId: string) {
|
||||
if (agentId === CLOUD_AGENT) {
|
||||
if (this.cloudStatus.logged_in && !this.cloudStatus.active_subscription) {
|
||||
return "You currently do not have an active Home Assistant Cloud subscription.";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.agents.cloud_agent_no_subcription"
|
||||
);
|
||||
}
|
||||
return "Note: It stores only one backup with a maximum size of 5 GB, regardless of your settings.";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.agents.cloud_agent_description"
|
||||
);
|
||||
}
|
||||
if (isNetworkMountAgent(agentId)) {
|
||||
return "Network storage";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.agents.network_mount_agent_description"
|
||||
);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -107,7 +113,7 @@ class HaBackupConfigAgents extends LitElement {
|
||||
slot="start"
|
||||
/>
|
||||
`}
|
||||
<div slot="headline">${name}</div>
|
||||
<div slot="headline" class="name">${name}</div>
|
||||
${description
|
||||
? html`<div slot="supporting-text">${description}</div>`
|
||||
: nothing}
|
||||
@ -124,7 +130,9 @@ class HaBackupConfigAgents extends LitElement {
|
||||
})}
|
||||
</ha-md-list>
|
||||
`
|
||||
: html`<p>No sync agents configured</p>`}
|
||||
: html`<p>
|
||||
${this.hass.localize("ui.panel.config.backup.agents.no_agents")}
|
||||
</p>`}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -157,6 +165,12 @@ class HaBackupConfigAgents extends LitElement {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-md-list-item .name {
|
||||
word-break: break-word;
|
||||
}
|
||||
ha-md-list-item img {
|
||||
width: 48px;
|
||||
}
|
||||
|
@ -158,11 +158,17 @@ class HaBackupConfigData extends LitElement {
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
|
||||
<span slot="headline"> Home Assistant settings </span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.panel.config.backup.data.ha_settings")}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.forceHomeAssistant
|
||||
? "The bare minimum needed to restore the system. It is always included in automatic backup data."
|
||||
: "The bare minimum needed to restore your system."}
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.data.ha_settings_included_description"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.backup.data.ha_settings_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
id="homeassistant"
|
||||
@ -175,9 +181,13 @@ class HaBackupConfigData extends LitElement {
|
||||
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiChartBox}></ha-svg-icon>
|
||||
<span slot="headline">History</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.panel.config.backup.data.history")}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Historical data of your sensors, including your energy dashboard.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.history_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
id="database"
|
||||
@ -194,9 +204,13 @@ class HaBackupConfigData extends LitElement {
|
||||
slot="start"
|
||||
.path=${mdiPlayBoxMultiple}
|
||||
></ha-svg-icon>
|
||||
<span slot="headline">Media</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.panel.config.backup.data.media")}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
This can include large filesize camera recordings.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.history_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
id="media"
|
||||
@ -208,10 +222,15 @@ class HaBackupConfigData extends LitElement {
|
||||
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiFolder}></ha-svg-icon>
|
||||
<span slot="headline">Share folder</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.share_folder"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Folder that is often used by add-ons for advanced or older
|
||||
configurations.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.share_folder_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
id="share"
|
||||
@ -228,9 +247,15 @@ class HaBackupConfigData extends LitElement {
|
||||
slot="start"
|
||||
.path=${mdiFolder}
|
||||
></ha-svg-icon>
|
||||
<span slot="headline">Local addons folder</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.local_addons"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Folder that contains the data of your local add-ons.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.local_addons_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
id="local_addons"
|
||||
@ -248,9 +273,15 @@ class HaBackupConfigData extends LitElement {
|
||||
slot="start"
|
||||
.path=${mdiPuzzle}
|
||||
></ha-svg-icon>
|
||||
<span slot="headline">Add-ons</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.addons"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Select what add-ons you want to include.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.addons_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@ -259,13 +290,25 @@ class HaBackupConfigData extends LitElement {
|
||||
.value=${data.addons_mode}
|
||||
>
|
||||
<ha-md-select-option value="all">
|
||||
<div slot="headline">All</div>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.addons_all"
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option value="none">
|
||||
<div slot="headline">None</div>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.addons_none"
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option value="custom">
|
||||
<div slot="headline">Custom</div>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.data.addons_custom"
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
</ha-md-select>
|
||||
</ha-md-list-item>
|
||||
@ -327,6 +370,9 @@ class HaBackupConfigData extends LitElement {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-md-select {
|
||||
min-width: 210px;
|
||||
}
|
||||
|
@ -26,29 +26,55 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
return html`
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Download emergency kit</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
We recommend to save this encryption key somewhere secure.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._download}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
Download
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Show my encryption key</span>
|
||||
<span slot="supporting-text">
|
||||
Please keep your encryption key private.
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.show_encryption_key"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._show}>Show</ha-button>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.show_encryption_key_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._show}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.show_encryption_key_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Change encryption key</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.change_encryption_key"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
All next backups will use this encryption key.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.change_encryption_key_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button class="danger" slot="end" @click=${this._change}>
|
||||
Change
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.change_encryption_key_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
@ -58,11 +84,21 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
return html`
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Set encryption key</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.set_encryption_key"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">
|
||||
Set an encryption key for your backups.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.set_encryption_key_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._set}> Set </ha-button>
|
||||
<ha-button slot="end" @click=${this._set}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.set_encryption_key_action"
|
||||
)}</ha-button
|
||||
>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
@ -102,6 +138,9 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
.danger {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
@ -43,6 +43,23 @@ const RETENTION_PRESETS: Record<
|
||||
forever: { type: "days", value: 0 },
|
||||
};
|
||||
|
||||
const SCHEDULE_OPTIONS = [
|
||||
BackupScheduleState.DAILY,
|
||||
BackupScheduleState.MONDAY,
|
||||
BackupScheduleState.TUESDAY,
|
||||
BackupScheduleState.WEDNESDAY,
|
||||
BackupScheduleState.THURSDAY,
|
||||
BackupScheduleState.FRIDAY,
|
||||
BackupScheduleState.SATURDAY,
|
||||
BackupScheduleState.SUNDAY,
|
||||
] as const satisfies BackupScheduleState[];
|
||||
|
||||
const RETENTION_PRESETS_OPTIONS = [
|
||||
RetentionPreset.COPIES_3,
|
||||
RetentionPreset.FOREVER,
|
||||
RetentionPreset.CUSTOM,
|
||||
] as const satisfies RetentionPreset[];
|
||||
|
||||
const computeRetentionPreset = (
|
||||
data: RetentionData
|
||||
): RetentionPreset | undefined => {
|
||||
@ -128,7 +145,11 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
return html`
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Use automatic backups</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.use_automatic_backups"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@ -139,9 +160,15 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
${data.enabled
|
||||
? html`
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Schedule</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.schedule"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
How often you want to create a backup.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.schedule_description"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<ha-md-select
|
||||
@ -149,52 +176,47 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
@change=${this._scheduleChanged}
|
||||
.value=${data.schedule}
|
||||
>
|
||||
<ha-md-select-option .value=${BackupScheduleState.DAILY}>
|
||||
<div slot="headline">Daily at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.MONDAY}>
|
||||
<div slot="headline">Monday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.TUESDAY}>
|
||||
<div slot="headline">Tuesday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.WEDNESDAY}>
|
||||
<div slot="headline">Wednesday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.THURSDAY}>
|
||||
<div slot="headline">Thursday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.FRIDAY}>
|
||||
<div slot="headline">Friday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.SATURDAY}>
|
||||
<div slot="headline">Saturday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${BackupScheduleState.SUNDAY}>
|
||||
<div slot="headline">Sunday at ${time}</div>
|
||||
</ha-md-select-option>
|
||||
${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}`,
|
||||
{ time }
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
`
|
||||
)}
|
||||
</ha-md-select>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Backups to keep</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.schedule.retention`
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Based on the maximum number of backups or how many days they
|
||||
should be kept.
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.backup.schedule.retention_description`
|
||||
)}
|
||||
</span>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@change=${this._retentionPresetChanged}
|
||||
.value=${this._retentionPreset}
|
||||
>
|
||||
<ha-md-select-option .value=${RetentionPreset.COPIES_3}>
|
||||
<div slot="headline">3 backups</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${RetentionPreset.FOREVER}>
|
||||
<div slot="headline">All backups</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option .value=${RetentionPreset.CUSTOM}>
|
||||
<div slot="headline">Custom</div>
|
||||
</ha-md-select-option>
|
||||
${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
|
||||
@ -217,11 +239,17 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
.value=${data.retention.type}
|
||||
id="type"
|
||||
>
|
||||
<ha-md-select-option .value=${"days"}>
|
||||
<div slot="headline">days</div>
|
||||
<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"}>
|
||||
<div slot="headline">backups</div>
|
||||
<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>
|
||||
@ -320,6 +348,9 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
ha-md-select {
|
||||
min-width: 210px;
|
||||
}
|
||||
|
@ -77,7 +77,11 @@ export class HaBackupDataPicker extends LitElement {
|
||||
|
||||
if (data.homeassistant_included) {
|
||||
items.push({
|
||||
label: "Settings",
|
||||
label: data.database_included
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.data_picker.settings_and_history"
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.backup.data_picker.settings"),
|
||||
id: "config",
|
||||
version: data.homeassistant_version,
|
||||
});
|
||||
@ -99,8 +103,17 @@ export class HaBackupDataPicker extends LitElement {
|
||||
);
|
||||
|
||||
private _localizeFolder(folder: string): string {
|
||||
if (folder === "addons/local") {
|
||||
return "Local addons";
|
||||
switch (folder) {
|
||||
case "media":
|
||||
return this.hass.localize("ui.panel.config.backup.data_picker.media");
|
||||
case "share":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.data_picker.share_folder"
|
||||
);
|
||||
case "addons/local":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.data_picker.local_addons"
|
||||
);
|
||||
}
|
||||
return capitalizeFirstLetter(folder);
|
||||
}
|
||||
@ -226,7 +239,7 @@ export class HaBackupDataPicker extends LitElement {
|
||||
<ha-formfield>
|
||||
<ha-backup-formfield-label
|
||||
slot="label"
|
||||
.label=${"Home Assistant"}
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
>
|
||||
</ha-backup-formfield-label>
|
||||
@ -272,7 +285,9 @@ export class HaBackupDataPicker extends LitElement {
|
||||
<ha-formfield>
|
||||
<ha-backup-formfield-label
|
||||
slot="label"
|
||||
.label=${"Add-ons"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.data_picker.local_addons"
|
||||
)}
|
||||
.iconPath=${mdiPuzzle}
|
||||
>
|
||||
</ha-backup-formfield-label>
|
||||
|
@ -54,7 +54,9 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-card class="my-backups">
|
||||
<div class="card-header">My backups</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize("ui.panel.config.backup.overview.backups.title")}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
@ -63,10 +65,16 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${automaticStats.count} automatic backups
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.automatic",
|
||||
{ count: automaticStats.count }
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${bytesToString(automaticStats.size, 1)} in total
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.total_size",
|
||||
{ size: bytesToString(automaticStats.size, 1) }
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
@ -75,9 +83,17 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
href="/config/backup/backups?type=manual"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||
<div slot="headline">${manualStats.count} manual backups</div>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.manual",
|
||||
{ count: manualStats.count }
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
${bytesToString(manualStats.size, 1)} in total
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.total_size",
|
||||
{ size: bytesToString(manualStats.size, 1) }
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
@ -85,7 +101,11 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a href="/config/backup/backups?type=all">
|
||||
<ha-button>Show all backups</ha-button>
|
||||
<ha-button>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.backups.show_all"
|
||||
)}
|
||||
</ha-button>
|
||||
</a>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@ -31,17 +31,23 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
<div class="icon">
|
||||
<ha-svg-icon .path=${mdiInformationOutline}></ha-svg-icon>
|
||||
</div>
|
||||
Set up backups
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.onboarding.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Backups are essential for a reliable smart home. They help protect
|
||||
the work you've put into setting up your smart home, and if the
|
||||
worst happens, you can get back up and running quickly.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.onboarding.description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._setup}>Set up backups</ha-button>
|
||||
<ha-button @click=${this._setup}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.onboarding.setup"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
|
@ -11,73 +11,39 @@ export class HaBackupOverviewProgress extends LitElement {
|
||||
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
||||
|
||||
private get _heading() {
|
||||
switch (this.manager.manager_state) {
|
||||
case "create_backup":
|
||||
return "Creating backup";
|
||||
case "restore_backup":
|
||||
return "Restoring backup";
|
||||
case "receive_backup":
|
||||
return "Receiving backup";
|
||||
default:
|
||||
return "";
|
||||
const state = this.manager.manager_state;
|
||||
if (state === "idle") {
|
||||
return "";
|
||||
}
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.overview.progress.heading.${state}`
|
||||
);
|
||||
}
|
||||
|
||||
private get _description() {
|
||||
switch (this.manager.manager_state) {
|
||||
case "create_backup":
|
||||
switch (this.manager.stage) {
|
||||
case "addon_repositories":
|
||||
case "addons":
|
||||
return "Backing up add-ons";
|
||||
case "await_addon_restarts":
|
||||
return "Waiting for add-ons to restart";
|
||||
case "docker_config":
|
||||
return "Backing up Docker configuration";
|
||||
case "finishing_file":
|
||||
return "Finishing backup file";
|
||||
case "folders":
|
||||
return "Backing up folders";
|
||||
case "home_assistant":
|
||||
return "Backing up Home Assistant";
|
||||
case "upload_to_agents":
|
||||
return "Uploading to locations";
|
||||
default:
|
||||
return "";
|
||||
if (!this.manager.stage) {
|
||||
return "";
|
||||
}
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.overview.progress.description.create_backup.${this.manager.stage}`
|
||||
);
|
||||
case "restore_backup":
|
||||
switch (this.manager.stage) {
|
||||
case "addon_repositories":
|
||||
case "addons":
|
||||
return "Restoring add-ons";
|
||||
case "await_addon_restarts":
|
||||
return "Waiting for add-ons to restart";
|
||||
case "await_home_assistant_restart":
|
||||
return "Waiting for Home Assistant to restart";
|
||||
case "check_home_assistant":
|
||||
return "Checking Home Assistant";
|
||||
case "docker_config":
|
||||
return "Restoring Docker configuration";
|
||||
case "download_from_agent":
|
||||
return "Downloading from location";
|
||||
case "folders":
|
||||
return "Restoring folders";
|
||||
case "home_assistant":
|
||||
return "Restoring Home Assistant";
|
||||
case "remove_delta_addons":
|
||||
return "Removing delta add-ons";
|
||||
default:
|
||||
return "";
|
||||
if (!this.manager.stage) {
|
||||
return "";
|
||||
}
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.overview.progress.description.restore_backup.${this.manager.stage}`
|
||||
);
|
||||
|
||||
case "receive_backup":
|
||||
switch (this.manager.stage) {
|
||||
case "receive_file":
|
||||
return "Receiving file";
|
||||
case "upload_to_agents":
|
||||
return "Uploading to locations";
|
||||
default:
|
||||
return "";
|
||||
if (!this.manager.stage) {
|
||||
return "";
|
||||
}
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.overview.progress.description.receive_backup.${this.manager.stage}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
@ -34,42 +34,32 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
const { state: schedule } = config.schedule;
|
||||
|
||||
if (schedule === BackupScheduleState.NEVER) {
|
||||
return "Automatic backups are not scheduled";
|
||||
}
|
||||
|
||||
let copiesText = "and keep all backups";
|
||||
if (copies) {
|
||||
copiesText = `and keep the latest ${copies} backup(s)`;
|
||||
} else if (days) {
|
||||
copiesText = `and keep backups for ${days} day(s)`;
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.schedule_never"
|
||||
);
|
||||
}
|
||||
|
||||
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
||||
|
||||
let scheduleText = "";
|
||||
if (schedule === BackupScheduleState.DAILY) {
|
||||
scheduleText = `Daily at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.MONDAY) {
|
||||
scheduleText = `Weekly on Mondays at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.TUESDAY) {
|
||||
scheduleText = `Weekly on Tuesdays at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.WEDNESDAY) {
|
||||
scheduleText = `Weekly on Wednesdays at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.THURSDAY) {
|
||||
scheduleText = `Weekly on Thursdays at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.FRIDAY) {
|
||||
scheduleText = `Weekly on Fridays at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.SATURDAY) {
|
||||
scheduleText = `Weekly on Saturdays at ${time}`;
|
||||
}
|
||||
if (schedule === BackupScheduleState.SUNDAY) {
|
||||
scheduleText = `Weekly on Sundays at ${time}`;
|
||||
const scheduleText = this.hass.localize(
|
||||
`ui.panel.config.backup.overview.settings.schedule_${schedule}`,
|
||||
{ time }
|
||||
);
|
||||
|
||||
let copiesText = this.hass.localize(
|
||||
`ui.panel.config.backup.overview.settings.schedule_copies_all`,
|
||||
{ time }
|
||||
);
|
||||
if (copies) {
|
||||
copiesText = this.hass.localize(
|
||||
`ui.panel.config.backup.overview.settings.schedule_copies_backups`,
|
||||
{ count: copies }
|
||||
);
|
||||
} else if (days) {
|
||||
copiesText = this.hass.localize(
|
||||
`ui.panel.config.backup.overview.settings.schedule_copies_days`,
|
||||
{ count: days }
|
||||
);
|
||||
}
|
||||
|
||||
return scheduleText + " " + copiesText;
|
||||
@ -77,15 +67,23 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
|
||||
private _addonsDescription(config: BackupConfig): string {
|
||||
if (config.create_backup.include_all_addons) {
|
||||
return "All add-ons";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.addons_all"
|
||||
);
|
||||
}
|
||||
if (config.create_backup.include_addons?.length) {
|
||||
return `${config.create_backup.include_addons.length} add-ons`;
|
||||
const count = config.create_backup.include_addons?.length;
|
||||
if (count) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.addons_many",
|
||||
{ count }
|
||||
);
|
||||
}
|
||||
return "No add-ons";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.addons_none"
|
||||
);
|
||||
}
|
||||
|
||||
private _agentsDescription(config: BackupConfig): string {
|
||||
private _locationsDescription(config: BackupConfig): string {
|
||||
const hasLocal = config.create_backup.agent_ids.some((a) =>
|
||||
isLocalAgent(a)
|
||||
);
|
||||
@ -101,14 +99,24 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
offsiteLocations[0],
|
||||
offsiteLocations
|
||||
);
|
||||
return `Upload to ${name}`;
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.locations_one",
|
||||
{ name }
|
||||
);
|
||||
}
|
||||
return `Upload to ${offsiteLocations.length} off-site locations`;
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.locations_many",
|
||||
{ count: offsiteLocations.length }
|
||||
);
|
||||
}
|
||||
if (hasLocal) {
|
||||
return "Local backup only";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.locations_local_only"
|
||||
);
|
||||
}
|
||||
return "No location configured";
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.locations_none"
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -116,7 +124,11 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-card class="my-backups">
|
||||
<div class="card-header">Backup settings</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
@ -128,7 +140,9 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
${this._scheduleDescription(this.config)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
Automatic backup schedule and retention
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.schedule"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
@ -136,11 +150,17 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
<ha-svg-icon slot="start" .path=${mdiDatabase}></ha-svg-icon>
|
||||
<div slot="headline">
|
||||
${this.config.create_backup.include_database
|
||||
? "Settings and history"
|
||||
: "Settings only"}
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.data_settings_history"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.data_settings_only"
|
||||
)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
Home Assistant data that is included
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.data"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
@ -154,7 +174,11 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
<div slot="headline">
|
||||
${this._addonsDescription(this.config)}
|
||||
</div>
|
||||
<div slot="supporting-text">Add-ons that are included</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.addons"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
@ -164,9 +188,13 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
href="/config/backup/settings#locations"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiUpload}></ha-svg-icon>
|
||||
<div slot="headline">${this._agentsDescription(this.config)}</div>
|
||||
<div slot="headline">
|
||||
${this._locationsDescription(this.config)}
|
||||
</div>
|
||||
<div slot="supporting-text">
|
||||
Locations where backup is uploaded to
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.locations"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
@ -174,7 +202,9 @@ class HaBackupBackupsSummary extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._configure}>
|
||||
Configure backup settings
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.configure"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@ -49,37 +49,17 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
);
|
||||
});
|
||||
|
||||
private _nextBackupDescription(schedule: BackupScheduleState) {
|
||||
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
||||
|
||||
switch (schedule) {
|
||||
case BackupScheduleState.DAILY:
|
||||
return `Next automatic backup tomorrow at ${time}`;
|
||||
case BackupScheduleState.MONDAY:
|
||||
return `Next automatic backup next Monday at ${time}`;
|
||||
case BackupScheduleState.TUESDAY:
|
||||
return `Next automatic backup next Thuesday at ${time}`;
|
||||
case BackupScheduleState.WEDNESDAY:
|
||||
return `Next automatic backup next Wednesday at ${time}`;
|
||||
case BackupScheduleState.THURSDAY:
|
||||
return `Next automatic backup next Thursday at ${time}`;
|
||||
case BackupScheduleState.FRIDAY:
|
||||
return `Next automatic backup next Friday at ${time}`;
|
||||
case BackupScheduleState.SATURDAY:
|
||||
return `Next automatic backup next Saturday at ${time}`;
|
||||
case BackupScheduleState.SUNDAY:
|
||||
return `Next automatic backup next Sunday at ${time}`;
|
||||
default:
|
||||
return "No automatic backup scheduled";
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const now = new Date();
|
||||
|
||||
if (this.fetching) {
|
||||
return html`
|
||||
<ha-backup-summary-card heading="Loading backups" status="loading">
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.loading"
|
||||
)}
|
||||
status="loading"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
@ -96,8 +76,14 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
|
||||
const lastBackup = this._lastBackup(this.backups);
|
||||
|
||||
const nextBackupDescription = this._nextBackupDescription(
|
||||
this.config.schedule.state
|
||||
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
|
||||
@ -110,25 +96,50 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
|
||||
// If last attempt is after last completed backup, show error
|
||||
if (lastAttemptDate > lastCompletedDate) {
|
||||
const description = `The last automatic backup triggered ${relativeTime(lastAttemptDate, this.hass.locale, now, true)} wasn't successful.`;
|
||||
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||
const secondaryDescription = lastUploadedBackup
|
||||
? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.`
|
||||
: nextBackupDescription;
|
||||
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading="Last automatic backup failed"
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_heading"
|
||||
)}
|
||||
status="error"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">${description}</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_description",
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
lastAttemptDate,
|
||||
this.hass.locale,
|
||||
now,
|
||||
true
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span slot="headline">${secondaryDescription}</span>
|
||||
<span slot="headline">
|
||||
${lastUploadedBackup
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
new Date(lastUploadedBackup.date),
|
||||
this.hass.locale,
|
||||
now,
|
||||
true
|
||||
),
|
||||
count: lastUploadedBackup.agent_ids?.length ?? 0,
|
||||
}
|
||||
)
|
||||
: nextBackupDescription}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
@ -137,16 +148,21 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
|
||||
// If no backups yet, show warning
|
||||
if (!lastBackup) {
|
||||
const description = "You have no automatic backups yet.";
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading="No automatic backup available"
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.no_backup_heading"
|
||||
)}
|
||||
status="warning"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">${description}</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.no_backup_description"
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
@ -161,32 +177,68 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
|
||||
// If last backup
|
||||
if (lastBackup.failed_agent_ids?.length) {
|
||||
const description = `The last automatic backup created ${relativeTime(lastBackupDate, this.hass.locale, now, true)} wasn't stored in all locations.`;
|
||||
const lastUploadedBackup = this._lastUploadedBackup(this.backups);
|
||||
const secondaryDescription = lastUploadedBackup
|
||||
? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.`
|
||||
: nextBackupDescription;
|
||||
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading="Last automatic backup failed"
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_heading"
|
||||
)}
|
||||
status="error"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">${description}</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_backup_failed_locations_description",
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
lastAttemptDate,
|
||||
this.hass.locale,
|
||||
now,
|
||||
true
|
||||
),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
<span slot="headline">${secondaryDescription}</span>
|
||||
<span slot="headline">
|
||||
${lastUploadedBackup
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
new Date(lastUploadedBackup.date),
|
||||
this.hass.locale,
|
||||
now,
|
||||
true
|
||||
),
|
||||
count: lastUploadedBackup.agent_ids?.length ?? 0,
|
||||
}
|
||||
)
|
||||
: nextBackupDescription}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</ha-backup-summary-card>
|
||||
`;
|
||||
}
|
||||
|
||||
const description = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored in ${lastBackup.agent_ids?.length} locations.`;
|
||||
const lastSuccessfulBackupDescription = this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.last_successful_backup_description",
|
||||
{
|
||||
relative_time: relativeTime(
|
||||
new Date(lastBackup.date),
|
||||
this.hass.locale,
|
||||
now,
|
||||
true
|
||||
),
|
||||
count: lastBackup.agent_ids?.length ?? 0,
|
||||
}
|
||||
);
|
||||
|
||||
const numberOfDays = differenceInDays(
|
||||
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
|
||||
@ -202,13 +254,16 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
if (isOverdue) {
|
||||
return html`
|
||||
<ha-backup-summary-card
|
||||
heading=${`No backup for ${numberOfDays} days`}
|
||||
.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">${description}</span>
|
||||
<span slot="headline">${lastSuccessfulBackupDescription}</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
@ -220,11 +275,16 @@ class HaBackupOverviewBackups extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
||||
<ha-backup-summary-card
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.summary.backup_success_heading"
|
||||
)}
|
||||
status="success"
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||
<span slot="headline">${description}</span>
|
||||
<span slot="headline">${lastSuccessfulBackupDescription}</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||
|
@ -224,7 +224,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
@click=${this._done}
|
||||
.disabled=${!this._isStepValid()}
|
||||
>
|
||||
Save and create backup
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.save_and_create"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: html`
|
||||
@ -232,7 +234,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
@click=${this._nextStep}
|
||||
.disabled=${!this._isStepValid()}
|
||||
>
|
||||
Next
|
||||
${this.hass.localize("ui.common.next")}
|
||||
</ha-button>
|
||||
`}
|
||||
</div>
|
||||
@ -244,18 +246,14 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
|
||||
private get _stepTitle(): string {
|
||||
switch (this._step) {
|
||||
case "welcome":
|
||||
return "";
|
||||
case "key":
|
||||
return "Encryption key";
|
||||
case "setup":
|
||||
return "Set up your automatic backups";
|
||||
case "schedule":
|
||||
return "Automatic backups";
|
||||
case "data":
|
||||
return "Backup data";
|
||||
case "locations":
|
||||
return "Locations";
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.dialogs.onboarding.${this._step}.title`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@ -291,23 +289,24 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
src="/static/images/voice-assistant/hi.png"
|
||||
alt="Casita Home Assistant logo"
|
||||
/>
|
||||
<h1>Set up backups</h1>
|
||||
<h1>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.welcome.title"
|
||||
)}
|
||||
</h1>
|
||||
<p class="secondary">
|
||||
Backups are essential for a reliable smart home. They help protect
|
||||
the work you've put into setting up your smart home, and if the
|
||||
worst happens, you can get back up and running quickly. It is
|
||||
recommended that you create a backup every day. You should keep
|
||||
three backups in at least two different locations, one of which
|
||||
should be off-site.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.welcome.description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
case "key":
|
||||
return html`
|
||||
<p>
|
||||
All your backups are encrypted to keep your data private and secure.
|
||||
We recommend to save this key somewhere secure. As you can only
|
||||
restore your data with the backup encryption key.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.key.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._config.create_backup.password}</p>
|
||||
@ -318,13 +317,21 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Download emergency kit</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
We recommend to save this encryption key somewhere secure.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._downloadKey}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
Download
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
@ -333,16 +340,28 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
return html`
|
||||
<ha-md-list class="full">
|
||||
<ha-md-list-item type="button" @click=${this._done}>
|
||||
<span slot="headline">Recommended settings</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.setup.recommended_heading"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Backup everything daily, keeping three days of backups
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.setup.recommended_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"> </ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="button" @click=${this._nextStep}>
|
||||
<span slot="headline">Custom settings</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.setup.custom_heading"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Select when, where, and what to backup
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.setup.custom_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"> </ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
@ -351,8 +370,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
case "schedule":
|
||||
return html`
|
||||
<p>
|
||||
Let Home Assistant take care of your backups by creating a scheduled
|
||||
backup that also removes older backups.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.schedule.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-schedule
|
||||
.hass=${this.hass}
|
||||
@ -363,8 +383,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
case "data":
|
||||
return html`
|
||||
<p>
|
||||
Choose what data to include in your backups. You can always change
|
||||
this later.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.data.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-data
|
||||
.hass=${this.hass}
|
||||
@ -377,8 +398,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
case "locations":
|
||||
return html`
|
||||
<p>
|
||||
Home Assistant will upload to these locations when an automatic
|
||||
backup is made. You can use all locations for manual backups.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.onboarding.locations.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-agents
|
||||
.hass=${this.hass}
|
||||
|
@ -88,13 +88,11 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
const dialogTitle =
|
||||
this._step === "current"
|
||||
? "Save current encryption key"
|
||||
: this._step === "new"
|
||||
? "New encryption key"
|
||||
: this._step === "done"
|
||||
? "Save new encryption key"
|
||||
: "";
|
||||
this._step === "current" || this._step === "new"
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.backup.dialogs.change_encryption_key.${this._step}.title`
|
||||
)
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||
@ -119,7 +117,11 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
<div slot="content">${this._renderStepContent()}</div>
|
||||
<div slot="actions">
|
||||
${this._step === "current"
|
||||
? html`<ha-button @click=${this._nextStep}>Next</ha-button>`
|
||||
? html`
|
||||
<ha-button @click=${this._nextStep}>
|
||||
${this.hass.localize("ui.common.next")}
|
||||
</ha-button>
|
||||
`
|
||||
: this._step === "new"
|
||||
? html`
|
||||
<ha-button
|
||||
@ -127,10 +129,18 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
.disabled=${!this._newEncryptionKey}
|
||||
class="danger"
|
||||
>
|
||||
Change encryption key
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.change_encryption_key.actions.change"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: html`<ha-button @click=${this._done}>Done</ha-button>`}
|
||||
: html`
|
||||
<ha-button @click=${this._done}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.change_encryption_key.actions.done"
|
||||
)}
|
||||
</ha-button>
|
||||
`}
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
@ -141,9 +151,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
case "current":
|
||||
return html`
|
||||
<p>
|
||||
Make sure you have saved the current encryption key to make sure you
|
||||
have access to all your current backups. All next backups will use
|
||||
the new encryption key.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.change_encryption_key.current.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._params?.currentKey}</p>
|
||||
@ -154,13 +164,21 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Download old emergency kit</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_old_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
We recommend saving this encryption key file somewhere secure.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_old_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._downloadOld}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
Download
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_old_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
@ -168,22 +186,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
case "new":
|
||||
return html`
|
||||
<p>
|
||||
All next backups will use the new encryption key. Encryption keeps
|
||||
your backups private and secure.
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._newEncryptionKey}</p>
|
||||
<ha-icon-button
|
||||
.path=${mdiContentCopy}
|
||||
@click=${this._copyKeyToClipboard}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`;
|
||||
case "done":
|
||||
return html`<p>
|
||||
Keep this new encryption key in a safe place, as you will need it to
|
||||
access your backups, allowing it to be restored. Either record the
|
||||
characters below or download them as an emergency kit file.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.change_encryption_key.new.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._newEncryptionKey}</p>
|
||||
@ -194,16 +199,39 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Download new emergency kit</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
We recommend saving this encryption key file somewhere secure.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._downloadNew}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
Download
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>`;
|
||||
</ha-md-list>
|
||||
`;
|
||||
case "done":
|
||||
return html`
|
||||
<div class="done">
|
||||
<img
|
||||
src="/static/images/voice-assistant/hi.png"
|
||||
alt="Casita Home Assistant logo"
|
||||
/>
|
||||
<h1>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.change_encryption_key.done.title"
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
@ -306,6 +334,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.done {
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -164,8 +164,9 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dialogTitle =
|
||||
this._step === "sync" ? "Synchronization" : "Backup data";
|
||||
const dialogTitle = this.hass.localize(
|
||||
`ui.panel.config.backup.dialogs.generate.${this._step}.title`
|
||||
);
|
||||
|
||||
const isFirstStep = this._step === STEPS[0];
|
||||
const isLastStep = this._step === STEPS[STEPS.length - 1];
|
||||
@ -197,7 +198,11 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
</div>
|
||||
<div slot="actions">
|
||||
${isFirstStep
|
||||
? html`<ha-button @click=${this.closeDialog}>Cancel</ha-button>`
|
||||
? html`
|
||||
<ha-button @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
${isLastStep
|
||||
? html`
|
||||
@ -206,14 +211,19 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
.disabled=${this._formData.agents_mode === "custom" &&
|
||||
!selectedAgents.length}
|
||||
>
|
||||
Create backup
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.actions.create"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: html`<ha-button
|
||||
@click=${this._nextStep}
|
||||
.disabled=${this._step === "data" && this._noDataSelected}
|
||||
>Next</ha-button
|
||||
>`}
|
||||
: html`
|
||||
<ha-button
|
||||
@click=${this._nextStep}
|
||||
.disabled=${this._step === "data" && this._noDataSelected}
|
||||
>
|
||||
${this.hass.localize("ui.common.next")}
|
||||
</ha-button>
|
||||
`}
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
@ -266,16 +276,24 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
return html`
|
||||
<ha-textfield
|
||||
name="name"
|
||||
.label=${"Backup name"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.name"
|
||||
)}
|
||||
.value=${this._formData.name}
|
||||
@change=${this._nameChanged}
|
||||
>
|
||||
</ha-textfield>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Backup locations</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.locations"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
What locations you want to automatically backup to.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.locations_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-md-select
|
||||
slot="end"
|
||||
@ -287,10 +305,19 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
value="all"
|
||||
.disabled=${disabledAgentIds.length}
|
||||
>
|
||||
<div slot="headline">All (${this._agentIds.length})</div>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.locations_options.all",
|
||||
{ count: this._agentIds.length }
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
<ha-md-select-option value="custom">
|
||||
<div slot="headline">Custom</div>
|
||||
<div slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.locations_options.custom"
|
||||
)}
|
||||
</div>
|
||||
</ha-md-select-option>
|
||||
</ha-md-select>
|
||||
</ha-md-list-item>
|
||||
@ -299,16 +326,25 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="info"
|
||||
.title=${"Home Assistant Cloud cannot synchronize"}
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.ha_cloud_alert.title"
|
||||
)}
|
||||
>
|
||||
Add Home Assistant settings data to synchronize this backup to
|
||||
Home Assistant Cloud.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.ha_cloud_alert.description"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
${this._formData.agents_mode === "custom"
|
||||
? html`
|
||||
<ha-expansion-panel .header=${"Locations"} outlined expanded>
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.generate.sync.locations"
|
||||
)}
|
||||
outlined
|
||||
expanded
|
||||
>
|
||||
<ha-backup-agents-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._formData.agent_ids}
|
||||
|
@ -60,13 +60,17 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title">Backup now</span>
|
||||
<span slot="title">
|
||||
${this.hass.localize("ui.panel.config.backup.dialogs.new.title")}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">
|
||||
<ha-md-list
|
||||
innerRole="listbox"
|
||||
itemRoles="option"
|
||||
innerAriaLabel="Backup options"
|
||||
.innerAriaLabel=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.new.options"
|
||||
)}
|
||||
rootTabbable
|
||||
dialogInitialFocus
|
||||
>
|
||||
@ -76,17 +80,29 @@ class DialogNewBackup extends LitElement implements HassDialog {
|
||||
.disabled=${!this._params.config.create_backup.password}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiCalendarSync}></ha-svg-icon>
|
||||
<span slot="headline">Automatic backup</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.new.automatic.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Create a backup with the data and locations you have configured.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.new.automatic.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item @click=${this._manual} type="button">
|
||||
<ha-svg-icon slot="start" .path=${mdiGestureTap}></ha-svg-icon>
|
||||
<span slot="headline">Manual backup</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.new.manual.title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
Select data and locations for a manual backup.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.new.manual.description"
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
|
@ -122,7 +122,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dialogTitle = "Restore backup";
|
||||
const dialogTitle = this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.title"
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||
@ -146,7 +148,11 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
</div>
|
||||
<div slot="actions">
|
||||
${this._error
|
||||
? html`<ha-button @click=${this.closeDialog}>Close</ha-button>`
|
||||
? html`
|
||||
<ha-button @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</ha-button>
|
||||
`
|
||||
: this._step === "confirm" || this._step === "encryption"
|
||||
? this._renderConfirmActions()
|
||||
: nothing}
|
||||
@ -156,40 +162,71 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
private _renderConfirm() {
|
||||
return html`<p>
|
||||
Your backup will be restored and all current data will be overwritten.
|
||||
Depending on the size of the backup, this can take a while.
|
||||
</p>`;
|
||||
return html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.confirm.description"
|
||||
)}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEncryptionIntro() {
|
||||
if (this._usedUserInput) {
|
||||
return html`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.encryption.incorrect_key"
|
||||
)}
|
||||
`;
|
||||
}
|
||||
if (this._backupEncryptionKey) {
|
||||
return html`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.encryption.different_key"
|
||||
)}
|
||||
${this._params!.selectedData.homeassistant_included
|
||||
? html`
|
||||
<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.encryption.warning"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.encryption.description"
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEncryption() {
|
||||
return html`${this._usedUserInput
|
||||
? "The provided encryption key was incorrect, please try again."
|
||||
: this._backupEncryptionKey
|
||||
? html`The Backup is encrypted with a different encryption key than
|
||||
that is saved on this system. Please enter the encryption key for
|
||||
this backup.<br />
|
||||
${this._params!.selectedData.homeassistant_included
|
||||
? html`<ha-alert alert-type="warning"
|
||||
>After restoring the backup, your new backups will be
|
||||
encrypted with the encryption key that was present during
|
||||
the time of this backup.</ha-alert
|
||||
>`
|
||||
: nothing}`
|
||||
: "The backup is encrypted. Provide the encryption key to decrypt the backup."}
|
||||
return html`
|
||||
${this._renderEncryptionIntro()}
|
||||
|
||||
<ha-password-field
|
||||
@input=${this._passwordChanged}
|
||||
label="Encryption key"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.encryption.input_label"
|
||||
)}
|
||||
.value=${this._userPassword || ""}
|
||||
></ha-password-field>`;
|
||||
></ha-password-field>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderConfirmActions() {
|
||||
return html`<ha-button @click=${this.closeDialog}>Cancel</ha-button>
|
||||
<ha-button @click=${this._restoreBackup} class="destructive"
|
||||
>Restore</ha-button
|
||||
>`;
|
||||
return html`
|
||||
<ha-button @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button @click=${this._restoreBackup} class="destructive">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.actions.restore"
|
||||
)}
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderProgress() {
|
||||
@ -198,7 +235,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
<p>
|
||||
${this.hass.connected
|
||||
? this._restoreState()
|
||||
: "Restarting Home Assistant"}
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.progress.restarting"
|
||||
)}
|
||||
</p>
|
||||
</div>`;
|
||||
}
|
||||
@ -245,7 +284,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
this.closeDialog();
|
||||
}
|
||||
if (event.state === "failed") {
|
||||
this._error = "Backup restore failed";
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.restore_failed"
|
||||
);
|
||||
}
|
||||
if (event.state === "in_progress") {
|
||||
this._stage = event.stage;
|
||||
@ -263,29 +304,14 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
private _restoreState() {
|
||||
switch (this._stage) {
|
||||
case "addon_repositories":
|
||||
return "Restoring add-on repositories";
|
||||
case "addons":
|
||||
return "Restoring add-ons";
|
||||
case "await_addon_restarts":
|
||||
return "Waiting for add-ons to restart";
|
||||
case "await_home_assistant_restart":
|
||||
return "Waiting for Home Assistant to restart";
|
||||
case "check_home_assistant":
|
||||
return "Checking Home Assistant configuration";
|
||||
case "docker_config":
|
||||
return "Restoring Docker configuration";
|
||||
case "download_from_agent":
|
||||
return "Downloading backup";
|
||||
case "folders":
|
||||
return "Restoring folders";
|
||||
case "home_assistant":
|
||||
return "Restoring Home Assistant";
|
||||
case "remove_delta_addons":
|
||||
return "Removing add-ons that are no longer in the backup";
|
||||
if (!this._stage) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.restore.progress.restoring"
|
||||
);
|
||||
}
|
||||
return "Restoring backup";
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.backup.overview.progress.description.restore_backup.${this._stage}`
|
||||
);
|
||||
}
|
||||
|
||||
private async _doRestoreBackup(password?: string) {
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { mdiClose, mdiDownload, mdiKey } from "@mdi/js";
|
||||
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-button-prev";
|
||||
import "../../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
import "../../../../components/ha-md-list";
|
||||
@ -18,9 +20,12 @@ import {
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import type { SetBackupEncryptionKeyDialogParams } from "./show-dialog-set-backup-encryption-key";
|
||||
|
||||
const STEPS = ["new", "save"] as const;
|
||||
const STEPS = ["key", "done"] as const;
|
||||
|
||||
type Step = (typeof STEPS)[number];
|
||||
|
||||
@customElement("ha-dialog-set-backup-encryption-key")
|
||||
class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
@ -28,7 +33,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _step?: "new" | "save";
|
||||
@state() private _step?: Step;
|
||||
|
||||
@state() private _params?: SetBackupEncryptionKeyDialogParams;
|
||||
|
||||
@ -36,13 +41,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _newEncryptionKey?: string;
|
||||
|
||||
private _suggestedEncryptionKey?: string;
|
||||
|
||||
public showDialog(params: SetBackupEncryptionKeyDialogParams): void {
|
||||
this._params = params;
|
||||
this._step = STEPS[0];
|
||||
this._opened = true;
|
||||
this._suggestedEncryptionKey = generateEncryptionKey();
|
||||
this._newEncryptionKey = generateEncryptionKey();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@ -56,7 +59,6 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
this._newEncryptionKey = undefined;
|
||||
this._suggestedEncryptionKey = undefined;
|
||||
}
|
||||
|
||||
private _done() {
|
||||
@ -78,7 +80,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
const dialogTitle =
|
||||
this._step === "new" ? "Encryption key" : "Save new encryption key";
|
||||
this._step === "key"
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.backup.dialogs.set_encryption_key.key.title`
|
||||
)
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||
@ -93,18 +99,24 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
</ha-dialog-header>
|
||||
<div slot="content">${this._renderStepContent()}</div>
|
||||
<div slot="actions">
|
||||
${this._step === "new"
|
||||
${this._step === "key"
|
||||
? html`
|
||||
<ha-button
|
||||
@click=${this._submit}
|
||||
.disabled=${!this._newEncryptionKey}
|
||||
>
|
||||
Next
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.set_encryption_key.actions.set"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: this._step === "save"
|
||||
? html`<ha-button @click=${this._done}>Done</ha-button>`
|
||||
: nothing}
|
||||
: html`
|
||||
<ha-button @click=${this._done}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.set_encryption_key.actions.done"
|
||||
)}
|
||||
</ha-button>
|
||||
`}
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
@ -112,69 +124,76 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
private _renderStepContent() {
|
||||
switch (this._step) {
|
||||
case "new":
|
||||
case "key":
|
||||
return html`
|
||||
<p>
|
||||
All your backups are encrypted to keep your data private and secure.
|
||||
You need this encryption key to restore any backup.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.set_encryption_key.new.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-password-field
|
||||
placeholder="New encryption key"
|
||||
@input=${this._encryptionKeyChanged}
|
||||
.value=${this._newEncryptionKey || ""}
|
||||
></ha-password-field>
|
||||
<div class="encryption-key">
|
||||
<p>${this._newEncryptionKey}</p>
|
||||
<ha-icon-button
|
||||
.path=${mdiContentCopy}
|
||||
@click=${this._copyKeyToClipboard}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiKey}></ha-svg-icon>
|
||||
<span slot="headline">Use suggested encryption key</span>
|
||||
<span slot="supporting-text">
|
||||
${this._suggestedEncryptionKey}
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._useSuggestedEncryptionKey}>
|
||||
Enter
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._download}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
`;
|
||||
case "save":
|
||||
case "done":
|
||||
return html`
|
||||
<p>
|
||||
It’s important that you don’t lose this encryption key. We recommend
|
||||
to save this key somewhere secure. As you can only restore your data
|
||||
with the backup encryption key.
|
||||
</p>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Download emergency kit</span>
|
||||
<span slot="supporting-text">
|
||||
We recommend to save this encryption key somewhere secure.
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._downloadNew}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
Download
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<div class="done">
|
||||
<img
|
||||
src="/static/images/voice-assistant/hi.png"
|
||||
alt="Casita Home Assistant logo"
|
||||
/>
|
||||
<h1>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.set_encryption_key.done.title"
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private _downloadNew() {
|
||||
private async _copyKeyToClipboard() {
|
||||
await copyToClipboard(
|
||||
this._newEncryptionKey,
|
||||
this.renderRoot.querySelector("div")!
|
||||
);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
|
||||
private _download() {
|
||||
if (!this._newEncryptionKey) {
|
||||
return;
|
||||
}
|
||||
downloadEmergencyKit(this.hass, this._newEncryptionKey);
|
||||
}
|
||||
|
||||
private _encryptionKeyChanged(ev) {
|
||||
this._newEncryptionKey = ev.target.value;
|
||||
}
|
||||
|
||||
private _useSuggestedEncryptionKey() {
|
||||
this._newEncryptionKey = this._suggestedEncryptionKey;
|
||||
}
|
||||
|
||||
private async _submit() {
|
||||
if (!this._newEncryptionKey) {
|
||||
return;
|
||||
@ -190,7 +209,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
css`
|
||||
ha-md-dialog {
|
||||
width: 90vw;
|
||||
max-width: 500px;
|
||||
max-width: 560px;
|
||||
--dialog-content-padding: 8px 24px;
|
||||
}
|
||||
ha-md-list {
|
||||
@ -198,6 +217,30 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
.encryption-key {
|
||||
border: 1px solid var(--divider-color);
|
||||
background-color: var(--primary-background-color);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
.encryption-key p {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
font-family: "Roboto Mono", "Consolas", "Menlo", monospace;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
.encryption-key ha-icon-button {
|
||||
flex: none;
|
||||
margin: -16px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
max-width: none;
|
||||
@ -209,6 +252,9 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.done {
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -55,12 +55,17 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
.path=${mdiClose}
|
||||
@click=${this._closeDialog}
|
||||
></ha-icon-button>
|
||||
<span slot="title">Encryption key</span>
|
||||
<span slot="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
)}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">
|
||||
<p>
|
||||
Make sure you save the encryption key in a secure place so always
|
||||
have access to your backups.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._params?.currentKey}</p>
|
||||
@ -71,19 +76,29 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">Download emergency kit</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
We recommend saving this encryption key file somewhere secure.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._download}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="icon"></ha-svg-icon>
|
||||
Download
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this._closeDialog}>Close</ha-button>
|
||||
<ha-button @click=${this._closeDialog}>
|
||||
${this.hass.localize("ui.dialogs.generic.close")}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
@ -124,9 +139,6 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-button.danger {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
.encryption-key {
|
||||
border: 1px solid var(--divider-color);
|
||||
background-color: var(--primary-background-color);
|
||||
|
@ -86,7 +86,9 @@ export class DialogUploadBackup
|
||||
@click=${this.closeDialog}
|
||||
></ha-icon-button>
|
||||
|
||||
<span slot="title">Upload backup</span>
|
||||
<span slot="title">
|
||||
${this.hass.localize("ui.panel.config.backup.dialogs.upload.title")}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">
|
||||
${this._error
|
||||
@ -97,15 +99,21 @@ export class DialogUploadBackup
|
||||
.uploading=${this._uploading}
|
||||
.icon=${mdiFolderUpload}
|
||||
accept=${SUPPORTED_FORMAT}
|
||||
label="Select backup file"
|
||||
supports="Supports .tar files"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.input_label"
|
||||
)}
|
||||
.supports=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.supports_tar"
|
||||
)}
|
||||
@file-picked=${this._filePicked}
|
||||
></ha-file-upload>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this.closeDialog}>Cancel</ha-button>
|
||||
<ha-button @click=${this._upload} .disabled=${!this._formValid()}>
|
||||
Upload backup
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.action"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
@ -126,9 +134,13 @@ export class DialogUploadBackup
|
||||
const { file } = this._formData!;
|
||||
if (!file || file.type !== SUPPORTED_FORMAT) {
|
||||
showAlertDialog(this, {
|
||||
title: "Unsupported file format",
|
||||
text: "Please choose a Home Assistant backup file (.tar)",
|
||||
confirmText: "ok",
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.unsupported.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.unsupported.text"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.ok"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
@ -70,9 +69,9 @@ interface BackupRow extends DataTableRowData, BackupContent {
|
||||
formatted_type: string;
|
||||
}
|
||||
|
||||
type BackupType = "automatic" | "manual" | "imported";
|
||||
type BackupType = "automatic" | "manual";
|
||||
|
||||
const TYPE_ORDER: Array<BackupType> = ["automatic", "manual", "imported"];
|
||||
const TYPE_ORDER: Array<BackupType> = ["automatic", "manual"];
|
||||
|
||||
@customElement("ha-config-backup-backups")
|
||||
class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
@ -158,13 +157,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
relativeTime(new Date(backup.date), this.hass.locale),
|
||||
},
|
||||
formatted_type: {
|
||||
title: "Type",
|
||||
title: localize("ui.panel.config.backup.backup_type"),
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
groupable: true,
|
||||
},
|
||||
locations: {
|
||||
title: "Locations",
|
||||
title: localize("ui.panel.config.backup.locations"),
|
||||
showNarrow: true,
|
||||
minWidth: "60px",
|
||||
template: (backup) => html`
|
||||
@ -246,10 +245,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
})
|
||||
);
|
||||
|
||||
private _groupOrder = memoizeOne((activeGrouping: string | undefined) =>
|
||||
activeGrouping === "formatted_type"
|
||||
? TYPE_ORDER.map((type) => this._formatBackupType(type))
|
||||
: undefined
|
||||
private _groupOrder = memoizeOne(
|
||||
(activeGrouping: string | undefined, localize: LocalizeFunc) =>
|
||||
activeGrouping === "formatted_type"
|
||||
? TYPE_ORDER.map((type) =>
|
||||
localize(`ui.panel.config.backup.type.${type}`)
|
||||
)
|
||||
: undefined
|
||||
);
|
||||
|
||||
private _handleGroupingChanged(ev: CustomEvent) {
|
||||
@ -266,15 +268,11 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
this._selected = ev.detail.value;
|
||||
}
|
||||
|
||||
private _formatBackupType(type: BackupType): string {
|
||||
// Todo translate
|
||||
return capitalizeFirstLetter(type);
|
||||
}
|
||||
|
||||
private _data = memoizeOne(
|
||||
(
|
||||
backups: BackupContent[],
|
||||
filters: DataTableFiltersValues
|
||||
filters: DataTableFiltersValues,
|
||||
localize: LocalizeFunc
|
||||
): BackupRow[] => {
|
||||
const typeFilter = filters["ha-filter-states"] as string[] | undefined;
|
||||
let filteredBackups = backups;
|
||||
@ -286,12 +284,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
(!backup.with_automatic_settings && typeFilter.includes("manual"))
|
||||
);
|
||||
}
|
||||
return filteredBackups.map((backup) => ({
|
||||
...backup,
|
||||
formatted_type: this._formatBackupType(
|
||||
backup.with_automatic_settings ? "automatic" : "manual"
|
||||
),
|
||||
}));
|
||||
return filteredBackups.map((backup) => {
|
||||
const type = backup.with_automatic_settings ? "automatic" : "manual";
|
||||
return {
|
||||
...backup,
|
||||
formatted_type: localize(`ui.panel.config.backup.type.${type}`),
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@ -304,7 +303,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
has-fab
|
||||
.tabs=${[
|
||||
{
|
||||
name: "My backups",
|
||||
name: this.hass.localize("ui.panel.config.backup.backups.header"),
|
||||
path: `/config/backup/list`,
|
||||
},
|
||||
]}
|
||||
@ -326,14 +325,17 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
.selected=${this._selected.length}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
.initialCollapsedGroups=${this._activeCollapsed}
|
||||
.groupOrder=${this._groupOrder(this._activeGrouping)}
|
||||
.groupOrder=${this._groupOrder(
|
||||
this._activeGrouping,
|
||||
this.hass.localize
|
||||
)}
|
||||
@grouping-changed=${this._handleGroupingChanged}
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
.route=${this.route}
|
||||
@row-click=${this._showBackupDetails}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._data(this.backups, this._filters)}
|
||||
.data=${this._data(this.backups, this._filters, this.hass.localize)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.panel.config.backup.picker.search"
|
||||
@ -351,7 +353,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
@request-selected=${this._uploadBackup}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiUpload}></ha-svg-icon>
|
||||
Upload backup
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.backups.menu.upload_backup"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
@ -360,26 +364,32 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
${!this.narrow
|
||||
? html`
|
||||
<ha-button @click=${this._deleteSelected} class="warning">
|
||||
Delete selected
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.backups.delete_selected"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
.label=${"Delete selected"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.backups.delete_selected"
|
||||
)}
|
||||
.path=${mdiDelete}
|
||||
id="delete-btn"
|
||||
class="warning"
|
||||
@click=${this._deleteSelected}
|
||||
></ha-icon-button>
|
||||
<simple-tooltip animation-delay="0" for="delete-btn">
|
||||
Delete selected
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.backups.delete_selected"
|
||||
)}
|
||||
</simple-tooltip>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<ha-filter-states
|
||||
.hass=${this.hass}
|
||||
label="Type"
|
||||
.label=${this.hass.localize("ui.panel.config.backup.backup_type")}
|
||||
.value=${this._filters["ha-filter-states"]}
|
||||
.states=${this._states(this.hass.localize)}
|
||||
@data-table-filter-changed=${this._filterChanged}
|
||||
@ -392,7 +402,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
?disabled=${backupInProgress}
|
||||
.label=${"Backup now"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.backups.new_backup"
|
||||
)}
|
||||
extended
|
||||
@click=${this._newBackup}
|
||||
>
|
||||
@ -404,16 +416,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _states = memoizeOne((_localize: LocalizeFunc) => [
|
||||
{
|
||||
value: "automatic",
|
||||
label: "Automatic",
|
||||
},
|
||||
{
|
||||
value: "manual",
|
||||
label: "Manual",
|
||||
},
|
||||
]);
|
||||
private _states = memoizeOne((localize: LocalizeFunc) =>
|
||||
TYPE_ORDER.map((type) => ({
|
||||
value: type,
|
||||
label: localize(`ui.panel.config.backup.type.${type}`),
|
||||
}))
|
||||
);
|
||||
|
||||
private _filterChanged(ev) {
|
||||
const type = ev.target.localName;
|
||||
@ -489,8 +497,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
private async _deleteBackup(backup: BackupContent): Promise<void> {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: "Delete backup",
|
||||
text: "This backup will be permanently deleted.",
|
||||
title: this.hass.localize("ui.panel.config.backup.dialogs.delete.title"),
|
||||
text: this.hass.localize("ui.panel.config.backup.dialogs.delete.text"),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
});
|
||||
@ -499,17 +507,31 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteBackup(this.hass, backup.backup_id);
|
||||
if (this._selected.includes(backup.backup_id)) {
|
||||
this._selected = this._selected.filter((id) => id !== backup.backup_id);
|
||||
try {
|
||||
await deleteBackup(this.hass, backup.backup_id);
|
||||
if (this._selected.includes(backup.backup_id)) {
|
||||
this._selected = this._selected.filter((id) => id !== backup.backup_id);
|
||||
}
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.delete.failed"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
}
|
||||
|
||||
private async _deleteSelected() {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: "Delete selected backups",
|
||||
text: "These backups will be permanently deleted.",
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.delete_selected.title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.delete_selected.text"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
});
|
||||
@ -524,7 +546,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to delete backups",
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.delete_selected.failed"
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
return;
|
||||
|
@ -41,6 +41,7 @@ import { fileDownload } from "../../../util/file_download";
|
||||
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
|
||||
import "./components/ha-backup-data-picker";
|
||||
import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
type Agent = {
|
||||
id: string;
|
||||
@ -96,7 +97,8 @@ class HaConfigBackupDetails extends LitElement {
|
||||
back-path="/config/backup/backups"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this._backup?.name || "Backup"}
|
||||
.header=${this._backup?.name ||
|
||||
this.hass.localize("ui.panel.config.backup.details.header")}
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
|
||||
<ha-icon-button
|
||||
@ -118,44 +120,78 @@ class HaConfigBackupDetails extends LitElement {
|
||||
html`<ha-alert alert-type="error">${this._error}</ha-alert>`}
|
||||
${this._backup === null
|
||||
? html`
|
||||
<ha-alert alert-type="warning" title="Not found">
|
||||
Backup matching ${this.backupId} not found
|
||||
<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.backup.details.not_found"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.not_found_description",
|
||||
{ backupId: this.backupId }
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: !this._backup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: html`
|
||||
<ha-card>
|
||||
<div class="card-header">Backup</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list class="summary">
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.size"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${bytesToString(this._backup.size)}
|
||||
</span>
|
||||
<span slot="supporting-text">Size</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
${formatDateTime(
|
||||
new Date(this._backup.date),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)}
|
||||
<span slot="supporting-text">Created</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this._backup.protected
|
||||
? "Encrypted AES-128"
|
||||
: "Not encrypted"}
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.created"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${formatDateTime(
|
||||
new Date(this._backup.date),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)}
|
||||
</span>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.protection"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this._backup.protected
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.protected_encrypted_aes_128"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.backup.details.summary.protected_not_encrypted"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">Protected</span>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-header">Select what to restore</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.restore.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-backup-data-picker
|
||||
.hass=${this.hass}
|
||||
@ -172,12 +208,18 @@ class HaConfigBackupDetails extends LitElement {
|
||||
.disabled=${this._isRestoreDisabled()}
|
||||
class="danger"
|
||||
>
|
||||
Restore
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.restore.action"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-header">Locations</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.locations.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
${this._agents.map((agent) => {
|
||||
@ -187,11 +229,9 @@ class HaConfigBackupDetails extends LitElement {
|
||||
const name = computeBackupAgentName(
|
||||
this.hass.localize,
|
||||
agentId,
|
||||
this._backup!.agent_ids!
|
||||
this._backup!.agent_ids
|
||||
);
|
||||
|
||||
const isLocal = isLocalAgent(agentId);
|
||||
|
||||
return html`
|
||||
<ha-md-list-item>
|
||||
${isLocalAgent(agentId)
|
||||
@ -232,10 +272,12 @@ class HaConfigBackupDetails extends LitElement {
|
||||
</span>
|
||||
<span>
|
||||
${success
|
||||
? isLocal
|
||||
? "Backup created"
|
||||
: "Backup uploaded"
|
||||
: "Backup failed"}
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.backup.details.locations.backup_stored"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.backup.details.locations.backup_failed"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
${success
|
||||
@ -257,7 +299,9 @@ class HaConfigBackupDetails extends LitElement {
|
||||
slot="graphic"
|
||||
.path=${mdiDownload}
|
||||
></ha-svg-icon>
|
||||
Download from this location
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.details.locations.download"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
@ -309,7 +353,9 @@ class HaConfigBackupDetails extends LitElement {
|
||||
response.backup.failed_agent_ids || []
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Could not fetch backup details";
|
||||
this._error =
|
||||
err?.message ||
|
||||
this.hass.localize("ui.panel.config.backup.details.error");
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,8 +388,8 @@ class HaConfigBackupDetails extends LitElement {
|
||||
|
||||
private async _deleteBackup(): Promise<void> {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: "Delete backup",
|
||||
text: "This backup will be permanently deleted.",
|
||||
title: this.hass.localize("ui.panel.config.backup.dialogs.delete.title"),
|
||||
text: this.hass.localize("ui.panel.config.backup.dialogs.delete.text"),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
});
|
||||
@ -353,6 +399,7 @@ class HaConfigBackupDetails extends LitElement {
|
||||
}
|
||||
|
||||
await deleteBackup(this.hass, this._backup!.backup_id);
|
||||
fireEvent(this, "ha-refresh-backup-info");
|
||||
navigate("/config/backup");
|
||||
}
|
||||
|
||||
@ -388,6 +435,13 @@ class HaConfigBackupDetails extends LitElement {
|
||||
--mdc-icon-size: 48px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-md-list.summary ha-md-list-item {
|
||||
--md-list-item-supporting-text-size: 1rem;
|
||||
--md-list-item-label-text-size: 0.875rem;
|
||||
|
||||
--md-list-item-label-text-color: var(--secondary-text-color);
|
||||
--md-list-item-supporting-text-color: var(--primary-text-color);
|
||||
}
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ class HaConfigBackupOverview extends LitElement {
|
||||
back-path="/config/system"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${"Backup"}
|
||||
.header=${this.hass.localize("ui.panel.config.backup.overview.header")}
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-icon-button
|
||||
@ -140,7 +140,9 @@ class HaConfigBackupOverview extends LitElement {
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon" @request-selected=${this._uploadBackup}>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiUpload}></ha-svg-icon>
|
||||
Upload backup
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.menu.upload_backup"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div class="content">
|
||||
@ -190,7 +192,9 @@ class HaConfigBackupOverview extends LitElement {
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
?disabled=${backupInProgress}
|
||||
.label=${"Backup now"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.new_backup"
|
||||
)}
|
||||
extended
|
||||
@click=${this._newBackup}
|
||||
>
|
||||
|
@ -99,7 +99,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
back-path="/config/backup"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${"Backup settings"}
|
||||
.header=${this.hass.localize("ui.panel.config.backup.settings.header")}
|
||||
>
|
||||
${isComponentLoaded(this.hass, "hassio")
|
||||
? html`
|
||||
@ -117,7 +117,9 @@ class HaConfigBackupSettings extends LitElement {
|
||||
slot="graphic"
|
||||
.path=${mdiHarddisk}
|
||||
></ha-svg-icon>
|
||||
Change default action location
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.menu.change_default_location"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
`
|
||||
@ -125,11 +127,16 @@ class HaConfigBackupSettings extends LitElement {
|
||||
|
||||
<div class="content">
|
||||
<ha-card id="schedule">
|
||||
<div class="card-header">Automatic backups</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.schedule.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Let Home Assistant take care of your backups by creating a
|
||||
scheduled backup that also removes older backups.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.schedule.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-schedule
|
||||
.hass=${this.hass}
|
||||
@ -139,7 +146,11 @@ class HaConfigBackupSettings extends LitElement {
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card id="data">
|
||||
<div class="card-header">Backup data</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.data.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-backup-config-data
|
||||
.hass=${this.hass}
|
||||
@ -152,11 +163,16 @@ class HaConfigBackupSettings extends LitElement {
|
||||
</ha-card>
|
||||
|
||||
<ha-card class="agents" id="locations">
|
||||
<div class="card-header">Locations</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.locations.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Your backup will be stored on these locations when this default
|
||||
backup is created. You can use all locations for custom backups.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.locations.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-agents
|
||||
.hass=${this.hass}
|
||||
@ -165,23 +181,33 @@ class HaConfigBackupSettings extends LitElement {
|
||||
@value-changed=${this._agentsConfigChanged}
|
||||
></ha-backup-config-agents>
|
||||
${!this._config.create_backup.agent_ids.length
|
||||
? html`<ha-alert
|
||||
? html`
|
||||
<ha-alert
|
||||
alert-type="warning"
|
||||
title="No location selected"
|
||||
>You have to select at least one location to create a
|
||||
backup.</ha-alert
|
||||
><br />`
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.locations.no_location"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.locations.no_location_description"
|
||||
)}
|
||||
</ha-alert>
|
||||
<br />
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-header">Encryption key</div>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Keep this encryption key in a safe place, as you will need it to
|
||||
access your backup, allowing it to be restored. Download them as
|
||||
an emergency kit file and store it somewhere safe. Encryption
|
||||
keeps your backups private and secure.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-encryption-key
|
||||
.hass=${this.hass}
|
||||
|
@ -296,6 +296,7 @@ class HaScheduleForm extends LitElement {
|
||||
|
||||
const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config);
|
||||
newValue[day][index] = {
|
||||
...newValue[day][index],
|
||||
from: value.from,
|
||||
to:
|
||||
!isSameDay(start, end) || endFormatted === "0:00"
|
||||
@ -322,6 +323,7 @@ class HaScheduleForm extends LitElement {
|
||||
|
||||
const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config);
|
||||
const event = {
|
||||
...newValue[day][index],
|
||||
from: formatTime24h(start, this.hass.locale, this.hass.config),
|
||||
to:
|
||||
!isSameDay(start, end) || endFormatted === "0:00"
|
||||
|
@ -84,7 +84,9 @@ export class AssistPref extends LitElement {
|
||||
this._preferred = pipelines.preferred_pipeline;
|
||||
});
|
||||
this._pipelineEntitiesCount = Object.values(this.hass.entities).filter(
|
||||
(entity) => computeDomain(entity.entity_id) === "assist_satellite"
|
||||
(entity) =>
|
||||
computeDomain(entity.entity_id) === "assist_satellite" &&
|
||||
this.hass.states[entity.entity_id].state !== "unavailable"
|
||||
).length;
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ export class HuiViewBackgroundEditor extends LitElement {
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
translation_key:
|
||||
@ -65,6 +66,7 @@ export class HuiViewBackgroundEditor extends LitElement {
|
||||
},
|
||||
{
|
||||
name: "alignment",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
translation_key:
|
||||
@ -86,6 +88,7 @@ export class HuiViewBackgroundEditor extends LitElement {
|
||||
},
|
||||
{
|
||||
name: "repeat",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
translation_key:
|
||||
|
@ -2200,6 +2200,12 @@
|
||||
"size": "[%key:supervisor::backup::size%]",
|
||||
"created": "[%key:supervisor::backup::created%]",
|
||||
"no_backups": "[%key:supervisor::backup::no_backups%]",
|
||||
"backup_type": "Type",
|
||||
"type": {
|
||||
"manual": "Manual",
|
||||
"automatic": "Automatic"
|
||||
},
|
||||
"locations": "Locations",
|
||||
"create": {
|
||||
"title": "Create backup",
|
||||
"description": "Create a backup of your current configuration directory, this will take some time.",
|
||||
@ -2223,6 +2229,390 @@
|
||||
"name": "Default location"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete_selected": {
|
||||
"title": "Delete selected backups",
|
||||
"text": "These backups will be permanently deleted.",
|
||||
"failed": "Failed to delete selected backups"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Delete backup",
|
||||
"text": "This backup will be permanently deleted.",
|
||||
"failed": "Failed to delete backup"
|
||||
},
|
||||
"upload": {
|
||||
"title": "Upload backup",
|
||||
"action": "Upload backup",
|
||||
"input_label": "Select backup file",
|
||||
"supports_tar": "Supports .tar files",
|
||||
"unsupported": {
|
||||
"title": "Unsupported file format",
|
||||
"text": "Please choose a Home Assistant backup file (.tar)"
|
||||
}
|
||||
},
|
||||
"generate": {
|
||||
"sync": {
|
||||
"title": "Synchonization",
|
||||
"name": "Backup name",
|
||||
"locations": "Locations",
|
||||
"locations_description": "What locations you want to automatically backup to.",
|
||||
"locations_options": {
|
||||
"all": "All ({count})",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"ha_cloud_alert": {
|
||||
"title": "Home Assistant Cloud cannot synchronize",
|
||||
"description": "Add Home Assistant settings data to synchronize this backup to Home Assistant Cloud."
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"title": "Backup data"
|
||||
},
|
||||
"actions": {
|
||||
"create": "Create backup"
|
||||
}
|
||||
},
|
||||
"new": {
|
||||
"title": "Backup now",
|
||||
"options": "Backup options",
|
||||
"automatic": {
|
||||
"title": "Automatic backup",
|
||||
"description": "Create a backup with the data and locations you have configured."
|
||||
},
|
||||
"manual": {
|
||||
"title": "Manual backup",
|
||||
"description": "Select data and locations for a manual backup."
|
||||
}
|
||||
},
|
||||
"restore": {
|
||||
"title": "Restore backup",
|
||||
"restore_failed": "Backup restore failed",
|
||||
"confirm": {
|
||||
"description": "Your backup will be restored and all current data will be overwritten. Depending on the size of the backup, this can take a while."
|
||||
},
|
||||
"encryption": {
|
||||
"different_key": "The backup is encrypted. Provide the encryption key to decrypt the backup.",
|
||||
"incorrect_key": "The provided encryption key was incorrect, please try again.",
|
||||
"description": "The backup is encrypted with a different encryption key than the one stored on this system. Please enter the encryption key for this backup.",
|
||||
"warning": "After restoring this backup, all new backups will be encrypted using the same key that was used to restore this backup.",
|
||||
"input_label": "Encryption key"
|
||||
},
|
||||
"progress": {
|
||||
"restarting": "Restarting Home Assistant",
|
||||
"restoring": "Restoring backup"
|
||||
},
|
||||
"actions": {
|
||||
"restore": "Restore"
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"welcome": {
|
||||
"title": "[%key:ui::panel::config::backup::overview::onboarding::title%]",
|
||||
"description": "[%key:ui::panel::config::backup::overview::onboarding::description%]"
|
||||
},
|
||||
"key": {
|
||||
"title": "Encryption key",
|
||||
"description": "All your backups are encrypted to keep your data private and secure. We recommend saving this key somewhere secure. As you can only restore your data with this encryption key."
|
||||
},
|
||||
"setup": {
|
||||
"title": "Set up automatic backups",
|
||||
"recommended_heading": "Recommended",
|
||||
"recommended_description": "Backup everything daily, keeping three days of backups",
|
||||
"custom_heading": "Custom",
|
||||
"custom_description": "Select when, where, and what to backup"
|
||||
},
|
||||
"schedule": {
|
||||
"title": "Automatic backups",
|
||||
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups."
|
||||
},
|
||||
"data": {
|
||||
"title": "Backup data",
|
||||
"description": "Choose what data to include in your backups. You can always change this later."
|
||||
},
|
||||
"locations": {
|
||||
"title": "Locations",
|
||||
"description": "Home Assistant will upload to these locations when an automatic backup is made. You can use all locations for manual backups."
|
||||
},
|
||||
"save_and_create": "Save and create backup"
|
||||
},
|
||||
"change_encryption_key": {
|
||||
"current": {
|
||||
"title": "Save current encryption key",
|
||||
"description": "Backups made before this new encryption key was issued will continue to use the old key. Be sure to save the old key along with the new key to ensure that you can access all backups."
|
||||
},
|
||||
"new": {
|
||||
"title": "New encryption key",
|
||||
"description": "All future backups will use the new encryption key. Encryption keeps your backups private and secure."
|
||||
},
|
||||
"done": {
|
||||
"title": "Encryption key changed"
|
||||
},
|
||||
"actions": {
|
||||
"change": "Change encryption key",
|
||||
"done": "Done"
|
||||
}
|
||||
},
|
||||
"set_encryption_key": {
|
||||
"key": {
|
||||
"title": "Set encryption key",
|
||||
"description": "All your backups are encrypted to keep your data private and secure. We recommend saving this key somewhere secure. As you can only restore your data with the backup encryption key."
|
||||
},
|
||||
"done": {
|
||||
"title": "Encryption key set"
|
||||
},
|
||||
"actions": {
|
||||
"set": "Set encryption key",
|
||||
"done": "[%key:ui::panel::config::backup::dialogs::change_encryption_key::actions::done%]"
|
||||
}
|
||||
},
|
||||
"show_encryption_key": {
|
||||
"title": "Encryption key",
|
||||
"description": "Make sure you save the encryption key in a secure place so you always have access to your backups."
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"cloud_agent_description": "Note: It stores only the most recent backup, regardless of your retention settings, with a maximum size of 5 GB.",
|
||||
"cloud_agent_no_subcription": "You currently do not have an active Home Assistant Cloud subscription.",
|
||||
"network_mount_agent_description": "Network storage",
|
||||
"no_agents": "No locations configured",
|
||||
"local_agent": "This system"
|
||||
},
|
||||
"data": {
|
||||
"ha_settings": "Home Assistant settings",
|
||||
"ha_settings_description": "The bare minimum needed to restore the system.",
|
||||
"ha_settings_included_description": "The bare minimum needed to restore the system. It is always included in automatic backup data.",
|
||||
"history": "History",
|
||||
"history_description": "This can include large filesize camera recordings.",
|
||||
"media": "Media",
|
||||
"media_description": "For example, camera recordings.",
|
||||
"share_folder": "Share folder",
|
||||
"share_folder_description": "Folder that is often used by add-ons for advanced or older configurations.",
|
||||
"local_addons": "Local add-ons folder",
|
||||
"local_addons_description": "Folder that contains the data of your local add-ons.",
|
||||
"addons": "Add-ons",
|
||||
"addons_description": "Select what add-ons you want to include.",
|
||||
"addons_all": "All",
|
||||
"addons_none": "None",
|
||||
"addons_custom": "Custom"
|
||||
},
|
||||
"data_picker": {
|
||||
"settings": "Settings",
|
||||
"settings_and_history": "Settings and history",
|
||||
"media": "Media",
|
||||
"share_folder": "Share folder",
|
||||
"local_addons": "Local add-ons folder",
|
||||
"addons": "Add-ons"
|
||||
},
|
||||
"schedule": {
|
||||
"use_automatic_backups": "Use automatic backups",
|
||||
"schedule": "Schedule",
|
||||
"schedule_description": "How often you want to create a backup.",
|
||||
"schedule_options": {
|
||||
"daily": "Daily at {time}",
|
||||
"mon": "Monday at {time}",
|
||||
"tue": "Tuesday at {time}",
|
||||
"wed": "Wednesday at {time}",
|
||||
"thu": "Thursday at {time}",
|
||||
"fri": "Friday at {time}",
|
||||
"sat": "Saturday at {time}",
|
||||
"sun": "Sunday at {time}"
|
||||
},
|
||||
"retention": "Retention",
|
||||
"retention_description": "Based on the maximum number of backups or how many days they should be kept.",
|
||||
"retention_presets": {
|
||||
"copies_3": "3 backups",
|
||||
"forever": "Forever",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"retention_units": {
|
||||
"copies": "backups",
|
||||
"days": "days"
|
||||
}
|
||||
},
|
||||
"encryption_key": {
|
||||
"download_emergency_kit": "Download emergency kit",
|
||||
"download_emergency_kit_description": "We recommend to save this encryption key somewhere secure.",
|
||||
"download_emergency_kit_action": "Download",
|
||||
"download_old_emergency_kit": "Download old emergency kit",
|
||||
"download_old_emergency_kit_description": "[%key:ui::panel::config::backup::encryption_key::download_emergency_kit_description%]",
|
||||
"download_old_emergency_kit_action": "[%key:ui::panel::config::backup::encryption_key::download_emergency_kit_action%]",
|
||||
"show_encryption_key": "Show my encryption key",
|
||||
"show_encryption_key_description": "Please keep your encryption key private.",
|
||||
"show_encryption_key_action": "Show",
|
||||
"change_encryption_key": "Change encryption key",
|
||||
"change_encryption_key_description": "All future backups will use this encryption key.",
|
||||
"change_encryption_key_action": "Change",
|
||||
"set_encryption_key": "Set encryption key",
|
||||
"set_encryption_key_description": "Set an encryption key for your backups.",
|
||||
"set_encryption_key_action": "Set"
|
||||
},
|
||||
"emergency_kit_file": {
|
||||
"title": "Home Assistant Backup Emergency Kit",
|
||||
"description": "This emergency kit contains your backup encryption key. You need this key to be able to restore your Home Assistant backups.",
|
||||
"date": "Date:",
|
||||
"instance": "Instance:",
|
||||
"url": "URL:",
|
||||
"encryption_key": "Encryption key:",
|
||||
"more_info": "For more information, visit {link}"
|
||||
},
|
||||
"overview": {
|
||||
"header": "Backup",
|
||||
"menu": {
|
||||
"upload_backup": "Upload backup"
|
||||
},
|
||||
"new_backup": "Backup now",
|
||||
"onboarding": {
|
||||
"title": "Set up backups",
|
||||
"description": "Backups are essential for a reliable smart home. They help protect the work you've put into setting up your smart home, and if the worst happens, you can get back up and running quickly.",
|
||||
"setup": "Set up backups"
|
||||
},
|
||||
"progress": {
|
||||
"heading": {
|
||||
"create_backup": "Creating backup",
|
||||
"restore_backup": "Restoring backup",
|
||||
"receive_backup": "Receiving backup"
|
||||
},
|
||||
"description": {
|
||||
"create_backup": {
|
||||
"addon_repositories": "Backing up add-ons repositories",
|
||||
"addons": "Backing up add-ons",
|
||||
"await_addon_restarts": "Waiting for add-ons to restart",
|
||||
"docker_config": "Backing up Docker configuration",
|
||||
"finishing_file": "Finishing backup file",
|
||||
"folders": "Backing up folders",
|
||||
"home_assistant": "Backing up Home Assistant",
|
||||
"upload_to_agents": "Uploading to locations"
|
||||
},
|
||||
"restore_backup": {
|
||||
"addon_repositories": "Restoring add-ons repositories",
|
||||
"addons": "Restoring add-ons",
|
||||
"await_addon_restarts": "Waiting for add-ons to restart",
|
||||
"await_home_assistant_restart": "Waiting for Home Assistant to restart",
|
||||
"check_home_assistant": "Checking Home Assistant",
|
||||
"docker_config": "Restoring Docker configuration",
|
||||
"download_from_agent": "Downloading from location",
|
||||
"folders": "Restoring folders",
|
||||
"home_assistant": "Restoring Home Assistant",
|
||||
"remove_delta_addons": "Removing delta add-ons"
|
||||
},
|
||||
"receive_backup": {
|
||||
"receive_file": "Receiving file",
|
||||
"upload_to_agents": "Uploading to locations"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"next_backup_description": {
|
||||
"daily": "Next automatic backup tomorrow at {time}",
|
||||
"mon": "Next automatic backup next Monday at {time}",
|
||||
"tue": "Next automatic backup next Tuesday at {time}",
|
||||
"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...",
|
||||
"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_locations_description": "The last automatic backup created {relative_time} wasn't stored in all locations.",
|
||||
"last_successful_backup_description": "Last successful backup {relative_time} and stored in {count} {count, plural,\n one {location}\n other {locations}\n}.",
|
||||
"no_backup_heading": "No automatic backup available",
|
||||
"no_backup_description": "You have no automatic backups yet.",
|
||||
"backup_too_old_heading": "No backup for {count} {count, plural,\n one {day}\n other {days}\n}",
|
||||
"backup_success_heading": "Backed up"
|
||||
},
|
||||
"backups": {
|
||||
"title": "My backups",
|
||||
"automatic": "{count} automatic {count, plural,\n one {backup}\n other {backups}\n}",
|
||||
"manual": "{count} manual {count, plural,\n one {backup}\n other {backups}\n}",
|
||||
"total_size": "{size} in total",
|
||||
"show_all": "Show all backups"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Backup settings",
|
||||
"configure": "Configure backup settings",
|
||||
"schedule": "Automatic backup schedule and retention",
|
||||
"schedule_copies_all": "and keep all backups",
|
||||
"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_daily": "Daily at {time}",
|
||||
"schedule_mon": "Weekly on Mondays at {time}",
|
||||
"schedule_tue": "Weekly on Tuesdays at {time}",
|
||||
"schedule_wed": "Weekly on Wednesdays at {time}",
|
||||
"schedule_thu": "Weekly on Thursdays at {time}",
|
||||
"schedule_fri": "Weekly on Fridays at {time}",
|
||||
"schedule_sat": "Weekly on Saturdays at {time}",
|
||||
"schedule_sun": "Weekly on Sundays at {time}",
|
||||
"schedule_never": "Automatic backups are not scheduled",
|
||||
"data": "Home Assistant data that is included",
|
||||
"data_settings_history": "Settings and history",
|
||||
"data_settings_only": "Settings only",
|
||||
"addons": "Add-ons that are included",
|
||||
"addons_all": "All add-ons",
|
||||
"addons_many": "{count} {count, plural,\n one {add-on}\n other {add-ons}\n}",
|
||||
"addons_none": "No add-ons",
|
||||
"locations": "Locations where backup is stored to",
|
||||
"locations_one": "Store in {name}",
|
||||
"locations_many": "Store in {count} off-site {count, plural,\n one {location}\n other {locations}\n}",
|
||||
"locations_local_only": "Local backup only",
|
||||
"locations_none": "No locations configured"
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
"header": "My backups",
|
||||
"menu": {
|
||||
"upload_backup": "[%key:ui::panel::config::backup::overview::menu::upload_backup%]"
|
||||
},
|
||||
"delete_selected": "Delete selected",
|
||||
"new_backup": "[%key:ui::panel::config::backup::overview::new_backup%]"
|
||||
},
|
||||
"settings": {
|
||||
"header": "Backup settings",
|
||||
"menu": {
|
||||
"change_default_location": "Change default action location"
|
||||
},
|
||||
"schedule": {
|
||||
"title": "Automatic backups",
|
||||
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups."
|
||||
},
|
||||
"data": {
|
||||
"title": "Backup data"
|
||||
},
|
||||
"locations": {
|
||||
"title": "Locations",
|
||||
"description": "Your backup will be stored on these locations when this default backup is created. You can use all locations for custom backups.",
|
||||
"no_location": "No location selected",
|
||||
"no_location_description": "You have to select at least one location to create a backup."
|
||||
},
|
||||
"encryption_key": {
|
||||
"title": "Encryption key",
|
||||
"description": "Keep this encryption key in a safe place, as you will need it to access your backup, allowing it to be restored. Download it as an emergency kit file and store it somewhere safe. Encryption keeps your backups private and secure."
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"header": "Backup",
|
||||
"not_found": "Not found",
|
||||
"not_found_description": "Backup matching ''{backupId}'' not found",
|
||||
"error": "Could not fetch backup details",
|
||||
"summary": {
|
||||
"title": "Backup",
|
||||
"size": "Size",
|
||||
"created": "Created",
|
||||
"protection": "Protection",
|
||||
"protected_encrypted_aes_128": "Encrypted AES-128",
|
||||
"protected_not_encrypted": "Not encrypted"
|
||||
},
|
||||
"restore": {
|
||||
"title": "Selected what to restore",
|
||||
"action": "Restore"
|
||||
},
|
||||
"locations": {
|
||||
"title": "Locations",
|
||||
"backup_stored": "Backup stored",
|
||||
"backup_failed": "Backup failed",
|
||||
"download": "Download from this location"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2774,7 +3164,7 @@
|
||||
},
|
||||
"wakeword": {
|
||||
"title": "Streaming wake word engine",
|
||||
"description": " If a device supports streaming wake word engines, you can activate Assist by saying this word.",
|
||||
"description": "If a device supports streaming wake word engines, you can activate Assist by saying this word.",
|
||||
"note": "Most recent devices support on-device wake word engines and are configured on their device page."
|
||||
}
|
||||
},
|
||||
@ -2795,7 +3185,7 @@
|
||||
},
|
||||
"remote_access": {
|
||||
"title": "Remote access",
|
||||
"text": " Secure remote access to your system while supporting the development of Home Assistant."
|
||||
"text": "Secure remote access to your system while supporting the development of Home Assistant."
|
||||
}
|
||||
},
|
||||
"and_more": "And more",
|
||||
|
Loading…
x
Reference in New Issue
Block a user