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