mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20241223.1 (#23402)
This commit is contained in:
commit
e7d9032cc4
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@v2.2.0
|
uses: softprops/action-gh-release@v2.1.0
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
@ -107,7 +107,7 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@v2.2.0
|
uses: softprops/action-gh-release@v2.1.0
|
||||||
with:
|
with:
|
||||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|
||||||
@ -136,6 +136,6 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@v2.2.0
|
uses: softprops/action-gh-release@v2.1.0
|
||||||
with:
|
with:
|
||||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20241223.0"
|
version = "20241223.1"
|
||||||
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"
|
||||||
|
@ -22,14 +22,6 @@ export class HaOutlinedField extends MdOutlinedField {
|
|||||||
border-end-start-radius: var(--_container-shape-end-start);
|
border-end-start-radius: var(--_container-shape-end-start);
|
||||||
border-end-end-radius: var(--_container-shape-end-end);
|
border-end-end-radius: var(--_container-shape-end-end);
|
||||||
}
|
}
|
||||||
.with-start .start {
|
|
||||||
margin-inline-end: var(--ha-outlined-field-start-margin, 4px);
|
|
||||||
margin-inline-start: initial;
|
|
||||||
}
|
|
||||||
.with-end .end {
|
|
||||||
margin-inline-start: var(--ha-outlined-field-end-margin, 4px);
|
|
||||||
margin-inline-end: initial;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,9 @@ export class HaOutlinedTextField extends MdOutlinedTextField {
|
|||||||
--md-outlined-field-container-shape-end-end: 10px;
|
--md-outlined-field-container-shape-end-end: 10px;
|
||||||
--md-outlined-field-container-shape-end-start: 10px;
|
--md-outlined-field-container-shape-end-start: 10px;
|
||||||
--md-outlined-field-focus-outline-width: 1px;
|
--md-outlined-field-focus-outline-width: 1px;
|
||||||
--ha-outlined-field-start-margin: -4px;
|
--md-outlined-field-with-leading-content-leading-space: 8px;
|
||||||
--ha-outlined-field-end-margin: -4px;
|
--md-outlined-field-with-trailing-content-trailing-space: 8px;
|
||||||
|
--md-outlined-field-content-space: 8px;
|
||||||
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
|
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
|
||||||
}
|
}
|
||||||
.input {
|
.input {
|
||||||
|
@ -871,7 +871,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
background-color: var(
|
background-color: var(
|
||||||
--sidebar-menu-button-background-color,
|
--sidebar-menu-button-background-color,
|
||||||
var(--primary-background-color)
|
inherit
|
||||||
);
|
);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -173,7 +173,7 @@ class DialogAreaDetail extends LitElement {
|
|||||||
>
|
>
|
||||||
${entry
|
${entry
|
||||||
? this.hass.localize("ui.common.save")
|
? this.hass.localize("ui.common.save")
|
||||||
: this.hass.localize("ui.common.add")}
|
: this.hass.localize("ui.common.create")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
|
@ -237,7 +237,7 @@ class DialogFloorDetail extends LitElement {
|
|||||||
>
|
>
|
||||||
${entry
|
${entry
|
||||||
? this.hass.localize("ui.common.save")
|
? this.hass.localize("ui.common.save")
|
||||||
: this.hass.localize("ui.common.add")}
|
: this.hass.localize("ui.common.create")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
|
@ -51,7 +51,7 @@ class HaBackupConfigAgents extends LitElement {
|
|||||||
|
|
||||||
private _description(agentId: string) {
|
private _description(agentId: string) {
|
||||||
if (agentId === CLOUD_AGENT) {
|
if (agentId === CLOUD_AGENT) {
|
||||||
return "It stores one backup. The oldest backups are deleted.";
|
return "Note: It stores only one backup, regardless of your settings.";
|
||||||
}
|
}
|
||||||
if (isNetworkMountAgent(agentId)) {
|
if (isNetworkMountAgent(agentId)) {
|
||||||
return "Network storage";
|
return "Network storage";
|
||||||
|
@ -195,7 +195,7 @@ class HaBackupConfigData extends LitElement {
|
|||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
<span slot="headline">Media</span>
|
<span slot="headline">Media</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
For example, camera recordings.
|
This can include large filesize camera recordings.
|
||||||
</span>
|
</span>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
id="media"
|
id="media"
|
||||||
@ -209,7 +209,7 @@ class HaBackupConfigData extends LitElement {
|
|||||||
<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">Share folder</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
Folder that is often used for advanced or older
|
Folder that is often used by add-ons for advanced or older
|
||||||
configurations.
|
configurations.
|
||||||
</span>
|
</span>
|
||||||
<ha-switch
|
<ha-switch
|
||||||
|
@ -75,6 +75,7 @@ class HaBackupSummaryCard extends LitElement {
|
|||||||
row-gap: 8px;
|
row-gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@ -145,10 +146,6 @@ class HaBackupSummaryCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 550px) {
|
@media all and (max-width: 550px) {
|
||||||
.summary {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
.action {
|
.action {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import { differenceInDays } from "date-fns";
|
|
||||||
import { html, LitElement } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { formatShortDateTime } from "../../../../common/datetime/format_date_time";
|
|
||||||
import type { BackupContent } from "../../../../data/backup";
|
|
||||||
import type { ManagerStateEvent } from "../../../../data/backup_manager";
|
|
||||||
import type { HomeAssistant } from "../../../../types";
|
|
||||||
import "./ha-backup-summary-card";
|
|
||||||
|
|
||||||
@customElement("ha-backup-summary-status")
|
|
||||||
export class HaBackupSummaryProgress extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public manager!: ManagerStateEvent;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public backups!: BackupContent[];
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "has-action" })
|
|
||||||
public hasAction = false;
|
|
||||||
|
|
||||||
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
|
||||||
const sortedBackups = backups
|
|
||||||
// eslint-disable-next-line arrow-body-style
|
|
||||||
.filter((backup) => {
|
|
||||||
// TODO : only show backups with default flag
|
|
||||||
return backup.with_automatic_settings;
|
|
||||||
})
|
|
||||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
||||||
|
|
||||||
return sortedBackups[0] as BackupContent | undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
const lastBackup = this._lastBackup(this.backups);
|
|
||||||
|
|
||||||
if (!lastBackup) {
|
|
||||||
return html`
|
|
||||||
<ha-backup-summary-card
|
|
||||||
heading="No backup available"
|
|
||||||
description="You have not created any backups yet."
|
|
||||||
.hasAction=${this.hasAction}
|
|
||||||
status="warning"
|
|
||||||
>
|
|
||||||
<slot name="action" slot="action"></slot>
|
|
||||||
</ha-backup-summary-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastBackupDate = new Date(lastBackup.date);
|
|
||||||
const numberOfDays = differenceInDays(new Date(), lastBackupDate);
|
|
||||||
|
|
||||||
// TODO : Improve time format
|
|
||||||
const description = `Last successful backup ${formatShortDateTime(lastBackupDate, this.hass.locale, this.hass.config)} and synced to ${lastBackup.agent_ids?.length} locations`;
|
|
||||||
if (numberOfDays > 8) {
|
|
||||||
return html`
|
|
||||||
<ha-backup-summary-card
|
|
||||||
heading=${`No backup for ${numberOfDays} days`}
|
|
||||||
description=${description}
|
|
||||||
.hasAction=${this.hasAction}
|
|
||||||
status="warning"
|
|
||||||
>
|
|
||||||
<slot name="action" slot="action"></slot>
|
|
||||||
</ha-backup-summary-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<ha-backup-summary-card
|
|
||||||
heading=${`Backed up`}
|
|
||||||
description=${description}
|
|
||||||
.hasAction=${this.hasAction}
|
|
||||||
status="success"
|
|
||||||
>
|
|
||||||
<slot name="action" slot="action"></slot>
|
|
||||||
</ha-backup-summary-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-backup-summary-status": HaBackupSummaryProgress;
|
|
||||||
}
|
|
||||||
}
|
|
@ -109,9 +109,13 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
.card-content {
|
.card-content {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -31,21 +31,20 @@ 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 automatic backups
|
Set up backups
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>
|
<p>
|
||||||
Backups are essential to a reliable smart home. They protect your
|
Backups are essential for a reliable smart home. They help protect
|
||||||
setup against failures and allows you to quickly have a working
|
the work you've put into setting up your smart home, and if the
|
||||||
system again. It is recommended to create a daily backup and keep
|
worst happens, you can get back up and running quickly. It is
|
||||||
backups of the last 3 days on two different locations. And one of
|
recommended that you create a backup every day. You should keep
|
||||||
them is off-site.
|
three backups in at least two different locations, one of which
|
||||||
|
should be off-site.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-button @click=${this._setup}>
|
<ha-button @click=${this._setup}>Set up backups</ha-button>
|
||||||
Set up automatic backups
|
|
||||||
</ha-button>
|
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
|
@ -10,7 +10,11 @@ import "../../../../../components/ha-md-list";
|
|||||||
import "../../../../../components/ha-md-list-item";
|
import "../../../../../components/ha-md-list-item";
|
||||||
import "../../../../../components/ha-svg-icon";
|
import "../../../../../components/ha-svg-icon";
|
||||||
import type { BackupConfig } from "../../../../../data/backup";
|
import type { BackupConfig } from "../../../../../data/backup";
|
||||||
import { BackupScheduleState, isLocalAgent } from "../../../../../data/backup";
|
import {
|
||||||
|
BackupScheduleState,
|
||||||
|
computeBackupAgentName,
|
||||||
|
isLocalAgent,
|
||||||
|
} from "../../../../../data/backup";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
|
|
||||||
@ -88,6 +92,14 @@ class HaBackupBackupsSummary extends LitElement {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (offsiteLocations.length) {
|
if (offsiteLocations.length) {
|
||||||
|
if (offsiteLocations.length === 1) {
|
||||||
|
const name = computeBackupAgentName(
|
||||||
|
this.hass.localize,
|
||||||
|
offsiteLocations[0],
|
||||||
|
offsiteLocations
|
||||||
|
);
|
||||||
|
return `Upload to ${name}`;
|
||||||
|
}
|
||||||
return `Upload to ${offsiteLocations.length} off-site locations`;
|
return `Upload to ${offsiteLocations.length} off-site locations`;
|
||||||
}
|
}
|
||||||
if (hasLocal) {
|
if (hasLocal) {
|
||||||
@ -184,9 +196,13 @@ class HaBackupBackupsSummary extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
.card-content {
|
.card-content {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
||||||
import { differenceInDays, setHours, setMinutes } from "date-fns";
|
import { addHours, differenceInDays, setHours, setMinutes } from "date-fns";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -17,6 +17,8 @@ import { haStyle } from "../../../../../resources/styles";
|
|||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-backup-summary-card";
|
import "../ha-backup-summary-card";
|
||||||
|
|
||||||
|
const OVERDUE_MARGIN_HOURS = 3;
|
||||||
|
|
||||||
@customElement("ha-backup-overview-summary")
|
@customElement("ha-backup-overview-summary")
|
||||||
class HaBackupOverviewBackups extends LitElement {
|
class HaBackupOverviewBackups extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -25,9 +27,14 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public config!: BackupConfig;
|
@property({ attribute: false }) public config!: BackupConfig;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public fetching = false;
|
||||||
|
|
||||||
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
||||||
const sortedBackups = backups
|
const sortedBackups = backups
|
||||||
.filter((backup) => backup.with_automatic_settings)
|
.filter(
|
||||||
|
(backup) =>
|
||||||
|
backup.with_automatic_settings && !backup.failed_agent_ids?.length
|
||||||
|
)
|
||||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
|
|
||||||
return sortedBackups[0] as BackupContent | undefined;
|
return sortedBackups[0] as BackupContent | undefined;
|
||||||
@ -60,6 +67,23 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
if (this.fetching) {
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card heading="Loading backups" status="loading">
|
||||||
|
<ha-md-list>
|
||||||
|
<ha-md-list-item>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiBackupRestore}></ha-svg-icon>
|
||||||
|
<span slot="headline" class="skeleton"></span>
|
||||||
|
</ha-md-list-item>
|
||||||
|
<ha-md-list-item>
|
||||||
|
<ha-svg-icon slot="start" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
|
<span slot="headline" class="skeleton"></span>
|
||||||
|
</ha-md-list-item>
|
||||||
|
</ha-md-list>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
const lastBackup = this._lastBackup(this.backups);
|
const lastBackup = this._lastBackup(this.backups);
|
||||||
|
|
||||||
if (!lastBackup) {
|
if (!lastBackup) {
|
||||||
@ -75,10 +99,9 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
const lastBackupDate = new Date(lastBackup.date);
|
const lastBackupDate = new Date(lastBackup.date);
|
||||||
|
|
||||||
const numberOfDays = differenceInDays(new Date(), lastBackupDate);
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
const lastBackupDescription = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and synced to ${lastBackup.agent_ids?.length} locations.`;
|
const lastBackupDescription = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored to ${lastBackup.agent_ids?.length} locations.`;
|
||||||
const nextBackupDescription = this._nextBackupDescription(
|
const nextBackupDescription = this._nextBackupDescription(
|
||||||
this.config.schedule.state
|
this.config.schedule.state
|
||||||
);
|
);
|
||||||
@ -94,51 +117,62 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
heading=${`Last automatic backup failed`}
|
heading=${`Last automatic backup failed`}
|
||||||
status="error"
|
status="error"
|
||||||
>
|
>
|
||||||
<ul class="list">
|
<ha-md-list>
|
||||||
<li class="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>${lastAttemptDescription}</span>
|
<span slot="headline">${lastAttemptDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
<li class="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>${lastBackupDescription}</span>
|
<span slot="headline">${lastBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
</ul>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numberOfDays > 0) {
|
const numberOfDays = differenceInDays(
|
||||||
|
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
|
||||||
|
addHours(now, -OVERDUE_MARGIN_HOURS),
|
||||||
|
lastBackupDate
|
||||||
|
);
|
||||||
|
|
||||||
|
const isOverdue =
|
||||||
|
(numberOfDays >= 1 &&
|
||||||
|
this.config.schedule.state === BackupScheduleState.DAILY) ||
|
||||||
|
numberOfDays >= 7;
|
||||||
|
|
||||||
|
if (isOverdue) {
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-summary-card
|
||||||
heading=${`No backup for ${numberOfDays} days`}
|
heading=${`No backup for ${numberOfDays} days`}
|
||||||
status="warning"
|
status="warning"
|
||||||
>
|
>
|
||||||
<ul class="list">
|
<ha-md-list>
|
||||||
<li class="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>${lastBackupDescription}</span>
|
<span slot="headline">${lastBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
<li class="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>${nextBackupDescription}</span>
|
<span slot="headline">${nextBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
</ul>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
<ha-backup-summary-card heading=${`Backed up`} status="success">
|
||||||
<ul class="list">
|
<ha-md-list>
|
||||||
<li class="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>${lastBackupDescription}</span>
|
<span slot="headline">${lastBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
<li class="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>${nextBackupDescription}</span>
|
<span slot="headline">${nextBackupDescription}</span>
|
||||||
</li>
|
</ha-md-list-item>
|
||||||
</ul>
|
</ha-md-list>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-summary-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -156,25 +190,6 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 8px 24px 24px 24px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: 0.25px;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
@ -183,6 +198,42 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
ha-md-list {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
ha-md-list-item {
|
||||||
|
--md-list-item-top-space: 8px;
|
||||||
|
--md-list-item-bottom-space: 8px;
|
||||||
|
--md-list-item-one-line-container-height: 40x;
|
||||||
|
}
|
||||||
|
span.skeleton {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 160px;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: loading;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-duration: 1.2s;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 20px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
rgb(247, 249, 250) 8%,
|
||||||
|
rgb(235, 238, 240) 18%,
|
||||||
|
rgb(247, 249, 250) 33%
|
||||||
|
)
|
||||||
|
0% 0% / 936px 104px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
background-position: -468px 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 468px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -287,13 +287,14 @@ 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 your automatic backups</h1>
|
<h1>Set up backups</h1>
|
||||||
<p class="secondary">
|
<p class="secondary">
|
||||||
Backups are essential to a reliable smart home. They protect your
|
Backups are essential for a reliable smart home. They help protect
|
||||||
setup against failures and allows you to quickly have a working
|
the work you've put into setting up your smart home, and if the
|
||||||
system again. It is recommended to create a daily backup and keep
|
worst happens, you can get back up and running quickly. It is
|
||||||
backups of the last 3 days on two different locations. And one of
|
recommended that you create a backup every day. You should keep
|
||||||
them is off-site.
|
three backups in at least two different locations, one of which
|
||||||
|
should be off-site.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -327,21 +328,23 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
case "setup":
|
case "setup":
|
||||||
return html`
|
return html`
|
||||||
<p>
|
<p>
|
||||||
It is recommended to create a daily backup and keep backups of the
|
It is recommended that you create a backup every day. You should
|
||||||
last 3 days on two different locations. And one of them is off-site.
|
keep three backups in at least two different locations, one of which
|
||||||
|
should be off-site. Once you make your selection, your first backup
|
||||||
|
will begin.
|
||||||
</p>
|
</p>
|
||||||
<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">Recommended settings</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
Set the proven settings of daily backup.
|
Backup everything daily, keeping three days of backups
|
||||||
</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">Custom settings</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
Select your own automation, data and locations
|
Select when, where, and what to backup
|
||||||
</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>
|
||||||
|
@ -152,7 +152,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Download old emergency kit</span>
|
<span slot="headline">Download old emergency kit</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
We recommend to save this encryption key somewhere secure.
|
We recommend saving this encryption key file somewhere secure.
|
||||||
</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>
|
||||||
@ -164,9 +164,10 @@ 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. We recommend to
|
Keep this encryption key in a safe place, as you will need it to
|
||||||
save this key somewhere secure. As you can only restore your data
|
access your backup, allowing it to be restored. Either record the
|
||||||
with the backup encryption key.
|
characters below or download them as an emergency kit file.
|
||||||
|
Encryption keeps your backups private and secure.
|
||||||
</p>
|
</p>
|
||||||
<div class="encryption-key">
|
<div class="encryption-key">
|
||||||
<p>${this._newEncryptionKey}</p>
|
<p>${this._newEncryptionKey}</p>
|
||||||
@ -179,7 +180,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Download new emergency kit</span>
|
<span slot="headline">Download new emergency kit</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
We recommend to save this encryption key somewhere secure.
|
We recommend saving this encryption key file somewhere secure.
|
||||||
</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>
|
||||||
|
@ -125,7 +125,8 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
: !this._backup
|
: !this._backup
|
||||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||||
: html`
|
: html`
|
||||||
<ha-card header="Backup">
|
<ha-card>
|
||||||
|
<div class="card-header">Backup</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
@ -145,7 +146,8 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
</ha-md-list>
|
</ha-md-list>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
<ha-card header="Select what to restore">
|
<ha-card>
|
||||||
|
<div class="card-header">Select what to restore</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-backup-data-picker
|
<ha-backup-data-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -166,7 +168,8 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
</ha-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
<ha-card header="Locations">
|
<ha-card>
|
||||||
|
<div class="card-header">Locations</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
${this._agents.map((agent) => {
|
${this._agents.map((agent) => {
|
||||||
@ -355,7 +358,7 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
.card-content {
|
.card-content {
|
||||||
padding: 0 20px 8px 20px;
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -368,6 +371,7 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
ha-md-list-item {
|
ha-md-list-item {
|
||||||
--md-list-item-leading-space: 0;
|
--md-list-item-leading-space: 0;
|
||||||
--md-list-item-trailing-space: 0;
|
--md-list-item-trailing-space: 0;
|
||||||
|
--md-list-item-two-line-container-height: 64px;
|
||||||
}
|
}
|
||||||
ha-md-list-item img {
|
ha-md-list-item img {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
@ -410,6 +414,9 @@ class HaConfigBackupDetails extends LitElement {
|
|||||||
.dot.error {
|
.dot.error {
|
||||||
background-color: var(--error-color);
|
background-color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ import "../../../layouts/hass-tabs-subpage-data-table";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import "./components/ha-backup-summary-card";
|
import "./components/ha-backup-summary-card";
|
||||||
import "./components/ha-backup-summary-status";
|
|
||||||
import "./components/overview/ha-backup-overview-backups";
|
import "./components/overview/ha-backup-overview-backups";
|
||||||
import "./components/overview/ha-backup-overview-onboarding";
|
import "./components/overview/ha-backup-overview-onboarding";
|
||||||
import "./components/overview/ha-backup-overview-progress";
|
import "./components/overview/ha-backup-overview-progress";
|
||||||
@ -182,31 +181,23 @@ class HaConfigBackupOverview extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-backup-overview-progress>
|
</ha-backup-overview-progress>
|
||||||
`
|
`
|
||||||
: this.fetching
|
: this._needsOnboarding
|
||||||
? html`
|
? html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-overview-onboarding
|
||||||
heading="Loading backups"
|
.hass=${this.hass}
|
||||||
description="Your backup information is being retrieved."
|
@button-click=${this._handleOnboardingButtonClick}
|
||||||
status="loading"
|
|
||||||
>
|
>
|
||||||
</ha-backup-summary-card>
|
</ha-backup-overview-onboarding>
|
||||||
`
|
`
|
||||||
: this._needsOnboarding
|
: html`
|
||||||
? html`
|
<ha-backup-overview-summary
|
||||||
<ha-backup-overview-onboarding
|
.hass=${this.hass}
|
||||||
.hass=${this.hass}
|
.backups=${this.backups}
|
||||||
@button-click=${this._handleOnboardingButtonClick}
|
.config=${this.config}
|
||||||
>
|
.fetching=${this.fetching}
|
||||||
</ha-backup-overview-onboarding>
|
>
|
||||||
`
|
</ha-backup-overview-summary>
|
||||||
: html`
|
`}
|
||||||
<ha-backup-overview-summary
|
|
||||||
.hass=${this.hass}
|
|
||||||
.backups=${this.backups}
|
|
||||||
.config=${this.config}
|
|
||||||
>
|
|
||||||
</ha-backup-overview-summary>
|
|
||||||
`}
|
|
||||||
|
|
||||||
<ha-backup-overview-backups
|
<ha-backup-overview-backups
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -140,10 +140,10 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
<div class="card-header">Encryption key</div>
|
<div class="card-header">Encryption key</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>
|
<p>
|
||||||
All your backups are encrypted to keep your data private and
|
Keep this encryption key in a safe place, as you will need it to
|
||||||
secure. You need this key to restore a backup. It's important
|
access your backup, allowing it to be restored. Either record
|
||||||
that you don't lose this key, as no one else can restore your
|
the characters below or download them as an emergency kit file.
|
||||||
data.
|
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}
|
||||||
@ -257,6 +257,12 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
.alert {
|
.alert {
|
||||||
--mdc-theme-primary: var(--error-color);
|
--mdc-theme-primary: var(--error-color);
|
||||||
}
|
}
|
||||||
|
.card-header {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,9 +436,8 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
>
|
>
|
||||||
${this.narrow
|
${this.narrow
|
||||||
? html`
|
? html`
|
||||||
<div slot="header">
|
<div slot="header" class="header">
|
||||||
<search-input-outlined
|
<search-input-outlined
|
||||||
class="header"
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
@ -457,7 +456,6 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
></ha-integration-overflow-menu>
|
></ha-integration-overflow-menu>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<search-input-outlined
|
<search-input-outlined
|
||||||
class="header"
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
@ -982,6 +980,9 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
search-input-outlined {
|
search-input-outlined {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.search {
|
.search {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -59,6 +59,7 @@ export class HuiViewBackgroundEditor extends LitElement {
|
|||||||
translation_key:
|
translation_key:
|
||||||
"ui.panel.lovelace.editor.edit_view.background.size",
|
"ui.panel.lovelace.editor.edit_view.background.size",
|
||||||
options: ["auto", "cover", "contain"],
|
options: ["auto", "cover", "contain"],
|
||||||
|
mode: "dropdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -79,6 +80,7 @@ export class HuiViewBackgroundEditor extends LitElement {
|
|||||||
"bottom center",
|
"bottom center",
|
||||||
"bottom right",
|
"bottom right",
|
||||||
],
|
],
|
||||||
|
mode: "dropdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -89,6 +91,7 @@ export class HuiViewBackgroundEditor extends LitElement {
|
|||||||
translation_key:
|
translation_key:
|
||||||
"ui.panel.lovelace.editor.edit_view.background.repeat",
|
"ui.panel.lovelace.editor.edit_view.background.repeat",
|
||||||
options: ["repeat", "no-repeat"],
|
options: ["repeat", "no-repeat"],
|
||||||
|
mode: "dropdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -348,6 +348,7 @@
|
|||||||
"move": "Move",
|
"move": "Move",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"create": "Create",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
@ -2159,7 +2160,7 @@
|
|||||||
"edit_floor": "Edit floor",
|
"edit_floor": "Edit floor",
|
||||||
"delete_floor": "Delete floor",
|
"delete_floor": "Delete floor",
|
||||||
"confirm_delete": "Delete floor?",
|
"confirm_delete": "Delete floor?",
|
||||||
"confirm_delete_text": "Removing the floor will unassign all areas from it."
|
"confirm_delete_text": "Deleting the floor will unassign all areas from it."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
@ -4358,7 +4359,7 @@
|
|||||||
"error_delete": "Error deleting device",
|
"error_delete": "Error deleting device",
|
||||||
"picker": {
|
"picker": {
|
||||||
"search": "Search {number} devices",
|
"search": "Search {number} devices",
|
||||||
"state": "State",
|
"state": "Status",
|
||||||
"bulk_actions": {
|
"bulk_actions": {
|
||||||
"move_area": "Move to area",
|
"move_area": "Move to area",
|
||||||
"no_area": "No area",
|
"no_area": "No area",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user