20241223.1 (#23402)

This commit is contained in:
Bram Kragten 2024-12-23 15:38:28 +01:00 committed by GitHub
commit e7d9032cc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 206 additions and 217 deletions

View File

@ -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

View File

@ -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"

View File

@ -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;
}
`, `,
]; ];
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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>
`; `;

View File

@ -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>
`; `;

View File

@ -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";

View File

@ -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

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
} }
`, `,
]; ];

View File

@ -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>
`; `;

View File

@ -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;
} }
`, `,
]; ];

View File

@ -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;
}
}
`, `,
]; ];
} }

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
`; `;
} }

View File

@ -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}

View File

@ -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;
}
`; `;
} }

View File

@ -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;

View File

@ -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",
}, },
}, },
}, },

View File

@ -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",