diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 253159226c..4f9f0cea59 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -55,7 +55,7 @@ jobs: script/release - name: Upload release assets - uses: softprops/action-gh-release@v2.2.0 + uses: softprops/action-gh-release@v2.1.0 with: files: | dist/*.whl @@ -107,7 +107,7 @@ jobs: - name: Tar folder run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist . - name: Upload release asset - uses: softprops/action-gh-release@v2.2.0 + uses: softprops/action-gh-release@v2.1.0 with: files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz @@ -136,6 +136,6 @@ jobs: - name: Tar folder run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build . - name: Upload release asset - uses: softprops/action-gh-release@v2.2.0 + uses: softprops/action-gh-release@v2.1.0 with: files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz diff --git a/pyproject.toml b/pyproject.toml index a8fa10b8c7..a8d343ca8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20241223.0" +version = "20241223.1" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/components/ha-outlined-field.ts b/src/components/ha-outlined-field.ts index 88050e7734..694c9e538c 100644 --- a/src/components/ha-outlined-field.ts +++ b/src/components/ha-outlined-field.ts @@ -22,14 +22,6 @@ export class HaOutlinedField extends MdOutlinedField { border-end-start-radius: var(--_container-shape-end-start); 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; - } `, ]; } diff --git a/src/components/ha-outlined-text-field.ts b/src/components/ha-outlined-text-field.ts index 757478d0b3..026bce0b25 100644 --- a/src/components/ha-outlined-text-field.ts +++ b/src/components/ha-outlined-text-field.ts @@ -28,8 +28,9 @@ export class HaOutlinedTextField extends MdOutlinedTextField { --md-outlined-field-container-shape-end-end: 10px; --md-outlined-field-container-shape-end-start: 10px; --md-outlined-field-focus-outline-width: 1px; - --ha-outlined-field-start-margin: -4px; - --ha-outlined-field-end-margin: -4px; + --md-outlined-field-with-leading-content-leading-space: 8px; + --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); } .input { diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 01e84f32ca..0420fcc74a 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -871,7 +871,7 @@ class HaSidebar extends SubscribeMixin(LitElement) { border-bottom: 1px solid var(--divider-color); background-color: var( --sidebar-menu-button-background-color, - var(--primary-background-color) + inherit ); font-size: 20px; align-items: center; diff --git a/src/panels/config/areas/dialog-area-registry-detail.ts b/src/panels/config/areas/dialog-area-registry-detail.ts index 59dcedc6f2..ed4051ba98 100644 --- a/src/panels/config/areas/dialog-area-registry-detail.ts +++ b/src/panels/config/areas/dialog-area-registry-detail.ts @@ -173,7 +173,7 @@ class DialogAreaDetail extends LitElement { > ${entry ? this.hass.localize("ui.common.save") - : this.hass.localize("ui.common.add")} + : this.hass.localize("ui.common.create")} `; diff --git a/src/panels/config/areas/dialog-floor-registry-detail.ts b/src/panels/config/areas/dialog-floor-registry-detail.ts index 8b4bf29db4..6929c9ba75 100644 --- a/src/panels/config/areas/dialog-floor-registry-detail.ts +++ b/src/panels/config/areas/dialog-floor-registry-detail.ts @@ -237,7 +237,7 @@ class DialogFloorDetail extends LitElement { > ${entry ? this.hass.localize("ui.common.save") - : this.hass.localize("ui.common.add")} + : this.hass.localize("ui.common.create")} `; diff --git a/src/panels/config/backup/components/config/ha-backup-config-agents.ts b/src/panels/config/backup/components/config/ha-backup-config-agents.ts index 925b4f8f2d..2d0504a908 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-agents.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-agents.ts @@ -51,7 +51,7 @@ class HaBackupConfigAgents extends LitElement { private _description(agentId: string) { 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)) { return "Network storage"; diff --git a/src/panels/config/backup/components/config/ha-backup-config-data.ts b/src/panels/config/backup/components/config/ha-backup-config-data.ts index b925cfd93a..bae085447a 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-data.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-data.ts @@ -195,7 +195,7 @@ class HaBackupConfigData extends LitElement { > Media - For example, camera recordings. + This can include large filesize camera recordings. Share folder - Folder that is often used for advanced or older + Folder that is often used by add-ons for advanced or older configurations. { - 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` - - - - `; - } - - 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` - - - - `; - } - return html` - - - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-backup-summary-status": HaBackupSummaryProgress; - } -} diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts index 5c44183678..a667e1a4bc 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts @@ -109,9 +109,13 @@ class HaBackupOverviewBackups extends LitElement { display: flex; justify-content: flex-end; } + .card-header { + padding-bottom: 8px; + } .card-content { padding-left: 0; padding-right: 0; + padding-bottom: 0; } `, ]; diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts index b12fd9376f..ebce3ea554 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts @@ -31,21 +31,20 @@ class HaBackupOverviewBackups extends LitElement {
- Set up automatic backups + Set up backups

- Backups are essential to a reliable smart home. They protect your - setup against failures and allows you to quickly have a working - system again. It is recommended to create a daily backup and keep - backups of the last 3 days on two different locations. And one of - them is off-site. + Backups are essential for a reliable smart home. They help protect + the work you've put into setting up your smart home, and if the + worst happens, you can get back up and running quickly. It is + recommended that you create a backup every day. You should keep + three backups in at least two different locations, one of which + should be off-site.

- - Set up automatic backups - + Set up backups
`; diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts index dc3679f1ba..95ad7f1979 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts @@ -10,7 +10,11 @@ import "../../../../../components/ha-md-list"; import "../../../../../components/ha-md-list-item"; import "../../../../../components/ha-svg-icon"; 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 type { HomeAssistant } from "../../../../../types"; @@ -88,6 +92,14 @@ class HaBackupBackupsSummary extends LitElement { ); 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`; } if (hasLocal) { @@ -184,9 +196,13 @@ class HaBackupBackupsSummary extends LitElement { display: flex; justify-content: flex-end; } + .card-header { + padding-bottom: 8px; + } .card-content { padding-left: 0; padding-right: 0; + padding-bottom: 0; } `, ]; diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index 5882675bf7..cf09d19114 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -1,5 +1,5 @@ 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 { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; @@ -17,6 +17,8 @@ import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; import "../ha-backup-summary-card"; +const OVERDUE_MARGIN_HOURS = 3; + @customElement("ha-backup-overview-summary") class HaBackupOverviewBackups extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -25,9 +27,14 @@ class HaBackupOverviewBackups extends LitElement { @property({ attribute: false }) public config!: BackupConfig; + @property({ type: Boolean }) public fetching = false; + private _lastBackup = memoizeOne((backups: BackupContent[]) => { 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()); return sortedBackups[0] as BackupContent | undefined; @@ -60,6 +67,23 @@ class HaBackupOverviewBackups extends LitElement { } protected render() { + if (this.fetching) { + return html` + + + + + + + + + + + + + `; + } + const lastBackup = this._lastBackup(this.backups); if (!lastBackup) { @@ -75,10 +99,9 @@ class HaBackupOverviewBackups extends LitElement { const lastBackupDate = new Date(lastBackup.date); - const numberOfDays = differenceInDays(new Date(), lastBackupDate); 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( this.config.schedule.state ); @@ -94,51 +117,62 @@ class HaBackupOverviewBackups extends LitElement { heading=${`Last automatic backup failed`} status="error" > - + ${lastBackupDescription} + + `; } - 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` -
    -
  • + + - ${lastBackupDescription} -
  • -
  • + ${lastBackupDescription} + + - ${nextBackupDescription} -
  • -
+ ${nextBackupDescription} + +
`; } return html` -
    -
  • + + - ${lastBackupDescription} -
  • -
  • + ${lastBackupDescription} + + - ${nextBackupDescription} -
  • -
+ ${nextBackupDescription} + +
`; } @@ -156,25 +190,6 @@ class HaBackupOverviewBackups extends LitElement { p { 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 { flex: none; } @@ -183,6 +198,42 @@ class HaBackupOverviewBackups extends LitElement { justify-content: flex-end; 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; + } + } `, ]; } diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index b70ed88102..2a8ff15bf4 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -287,13 +287,14 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { src="/static/images/voice-assistant/hi.png" alt="Casita Home Assistant logo" /> -

Set up your automatic backups

+

Set up backups

- Backups are essential to a reliable smart home. They protect your - setup against failures and allows you to quickly have a working - system again. It is recommended to create a daily backup and keep - backups of the last 3 days on two different locations. And one of - them is off-site. + Backups are essential for a reliable smart home. They help protect + the work you've put into setting up your smart home, and if the + worst happens, you can get back up and running quickly. It is + recommended that you create a backup every day. You should keep + three backups in at least two different locations, one of which + should be off-site.

`; @@ -327,21 +328,23 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { case "setup": return html`

- It is recommended to create a daily backup and keep backups of the - last 3 days on two different locations. And one of them is off-site. + 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. Once you make your selection, your first backup + will begin.

Recommended settings - Set the proven settings of daily backup. + Backup everything daily, keeping three days of backups Custom settings - Select your own automation, data and locations + Select when, where, and what to backup diff --git a/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts index ac8ae0bae6..1020083e3a 100644 --- a/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts @@ -152,7 +152,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { Download old emergency kit - We recommend to save this encryption key somewhere secure. + We recommend saving this encryption key file somewhere secure. @@ -164,9 +164,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { case "new": return html`

- All next backups will use the new encryption key. We recommend to - save this key somewhere secure. As you can only restore your data - with the backup encryption key. + Keep this encryption key in a safe place, as you will need it to + access your backup, allowing it to be restored. Either record the + characters below or download them as an emergency kit file. + Encryption keeps your backups private and secure.

${this._newEncryptionKey}

@@ -179,7 +180,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { Download new emergency kit - We recommend to save this encryption key somewhere secure. + We recommend saving this encryption key file somewhere secure. diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index 9cb06c1e5c..248913fe5e 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -125,7 +125,8 @@ class HaConfigBackupDetails extends LitElement { : !this._backup ? html`` : html` - + +
Backup
@@ -145,7 +146,8 @@ class HaConfigBackupDetails extends LitElement {
- + +
Select what to restore
- + +
Locations
${this._agents.map((agent) => { @@ -355,7 +358,7 @@ class HaConfigBackupDetails extends LitElement { margin-bottom: 24px; } .card-content { - padding: 0 20px 8px 20px; + padding: 0 20px; } .card-actions { display: flex; @@ -368,6 +371,7 @@ class HaConfigBackupDetails extends LitElement { ha-md-list-item { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; + --md-list-item-two-line-container-height: 64px; } ha-md-list-item img { width: 48px; @@ -410,6 +414,9 @@ class HaConfigBackupDetails extends LitElement { .dot.error { background-color: var(--error-color); } + .card-header { + padding-bottom: 8px; + } `; } diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index b7621ab9f7..40614715a6 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -27,7 +27,6 @@ import "../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; 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-onboarding"; import "./components/overview/ha-backup-overview-progress"; @@ -182,31 +181,23 @@ class HaConfigBackupOverview extends LitElement { > ` - : this.fetching + : this._needsOnboarding ? html` - - + ` - : this._needsOnboarding - ? html` - - - ` - : html` - - - `} + : html` + + + `} Encryption key

- All your backups are encrypted to keep your data private and - secure. You need this key to restore a backup. It's important - that you don't lose this key, as no one else can restore your - data. + Keep this encryption key in a safe place, as you will need it to + access your backup, allowing it to be restored. Either record + the characters below or download them as an emergency kit file. + Encryption keeps your backups private and secure.

${this.narrow ? html` -
+