Use the update integration to provide updates

This commit is contained in:
Ludeeus 2022-03-04 09:45:20 +00:00
parent 604b79696e
commit 9ea8e13c87
8 changed files with 452 additions and 136 deletions

View File

@ -45,7 +45,6 @@ import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { SUPERVISOR_UPDATE_NAMES } from "../../../src/panels/config/dashboard/ha-config-updates";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { addonArchIsSupported, extractChangelog } from "../util/addon"; import { addonArchIsSupported, extractChangelog } from "../util/addon";
@ -57,6 +56,12 @@ declare global {
type updateType = "os" | "supervisor" | "core" | "addon"; type updateType = "os" | "supervisor" | "core" | "addon";
const SUPERVISOR_UPDATE_NAMES = {
core: "Home Assistant Core",
os: "Home Assistant Operating System",
supervisor: "Home Assistant Supervisor",
};
const changelogUrl = ( const changelogUrl = (
entry: updateType, entry: updateType,
version: string version: string

37
src/data/update.ts Normal file
View File

@ -0,0 +1,37 @@
import { HomeAssistant } from "../types";
export interface UpdateDescription {
identifier: string;
name: string;
domain: string;
current_version: string;
available_version: string;
changelog_content: string | null;
changelog_url: string | null;
icon_url: string | null;
supports_backup: boolean;
}
export interface SkipUpdateParams {
domain: string;
version: string;
identifier: string;
}
export interface PerformUpdateParams extends SkipUpdateParams {
backup?: boolean;
}
export const fetchUpdateInfo = (
hass: HomeAssistant
): Promise<UpdateDescription[]> => hass.callWS({ type: "update/info" });
export const skipUpdate = (
hass: HomeAssistant,
params: SkipUpdateParams
): Promise<void> => hass.callWS({ type: "update/skip", ...params });
export const performUpdate = (
hass: HomeAssistant,
params: PerformUpdateParams
): Promise<void> => hass.callWS({ type: "update/update", ...params });

View File

@ -0,0 +1,211 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTL } from "../../common/util/compute_rtl";
import "../../components/ha-alert";
import "../../components/ha-checkbox";
import "../../components/ha-circular-progress";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-faded";
import "../../components/ha-formfield";
import "../../components/ha-icon-button";
import "../../components/ha-markdown";
import {
performUpdate,
skipUpdate,
UpdateDescription,
} from "../../data/update";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { UpdateDialogParams } from "./show-ha-update-dialog";
@customElement("ha-update-dialog")
export class HaUpdateDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _opened = false;
@state() private _updating = false;
@state() private _error?: string;
@state() private _update!: UpdateDescription;
_refreshCallback!: () => void;
public async showDialog(dialogParams: UpdateDialogParams): Promise<void> {
this._opened = true;
this._update = dialogParams.update;
this._refreshCallback = dialogParams.refreshCallback;
}
public async closeDialog(): Promise<void> {
this._opened = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._opened) {
return html``;
}
return html`
<ha-dialog
open
@closed=${this.closeDialog}
scrimClickAction
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.updates.dialog.title", {
name: this._update.name,
})
)}
>
<div>
${this._error
? html`<ha-alert alert-type="error" .rtl=${computeRTL(this.hass)}>
${this._error}
</ha-alert>`
: ""}
${!this._updating
? html`
${this._update.changelog_content
? html`
<ha-faded>
<ha-markdown .content=${this._update.changelog_content}>
</ha-markdown>
</ha-faded>
`
: ""}
${this._update.changelog_url
? html`<a href=${this._update.changelog_url} target="_blank">
Full changelog
</a> `
: ""}
<p>
${this.hass.localize(
"ui.panel.config.updates.dialog.description",
{
name: this._update.name,
version: this._update.current_version,
newest_version: this._update.available_version,
}
)}
</p>
${this._update.supports_backup
? html`
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.updates.dialog.create_backup"
)}
>
<ha-checkbox checked></ha-checkbox>
</ha-formfield>
`
: ""}
`
: html`<ha-circular-progress alt="Updating" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this.hass.localize(
"ui.panel.config.updates.dialog.updating",
{
name: this._update.name,
version: this._update.available_version,
}
)}
</p>`}
</div>
${!this._updating
? html`
<mwc-button slot="secondaryAction" @click=${this._skipUpdate}>
${this.hass.localize("ui.common.skip")}
</mwc-button>
<mwc-button
.disabled=${this._updating}
slot="primaryAction"
@click=${this._performUpdate}
>
${this.hass.localize("ui.panel.config.updates.dialog.update")}
</mwc-button>
`
: ""}
</ha-dialog>
`;
}
get _shouldCreateBackup(): boolean {
if (!this._update.supports_backup) {
return false;
}
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
if (checkbox) {
return checkbox.checked;
}
return true;
}
private async _performUpdate() {
this._error = undefined;
this._updating = true;
try {
await performUpdate(this.hass, {
domain: this._update.domain,
identifier: this._update.identifier,
version: this._update.available_version,
backup: this._shouldCreateBackup,
});
} catch (err: any) {
this._error = err.message;
this._updating = false;
return;
}
this._updating = false;
this._refreshCallback();
this.closeDialog();
}
private async _skipUpdate() {
this._error = undefined;
try {
await skipUpdate(this.hass, {
domain: this._update.domain,
identifier: this._update.identifier,
version: this._update.available_version,
});
} catch (err: any) {
this._error = err.message;
return;
}
this._refreshCallback();
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
ha-markdown {
padding-bottom: 8px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-update-dialog": HaUpdateDialog;
}
}

View File

@ -0,0 +1,18 @@
import { fireEvent } from "../../common/dom/fire_event";
import { UpdateDescription } from "../../data/update";
export interface UpdateDialogParams {
update: UpdateDescription;
refreshCallback: () => void;
}
export const showUpdateDialog = (
element: HTMLElement,
dialogParams: UpdateDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-update-dialog",
dialogImport: () => import("./ha-update-dialog"),
dialogParams,
});
};

View File

@ -26,10 +26,6 @@ import "../../../components/ha-menu-button";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { CloudStatus } from "../../../data/cloud"; import { CloudStatus } from "../../../data/cloud";
import {
refreshSupervisorAvailableUpdates,
SupervisorAvailableUpdates,
} from "../../../data/supervisor/root";
import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar"; import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
@ -38,10 +34,11 @@ import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "./ha-config-navigation"; import "./ha-config-navigation";
import "./ha-config-updates"; import "./ha-config-updates";
import { fireEvent } from "../../../common/dom/fire_event";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { showToast } from "../../../util/toast";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import { UpdateDescription } from "../../../data/update";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeRTL } from "../../../common/util/compute_rtl";
const randomTip = (hass: HomeAssistant) => { const randomTip = (hass: HomeAssistant) => {
const weighted: string[] = []; const weighted: string[] = [];
@ -114,14 +111,12 @@ class HaConfigDashboard extends LitElement {
@property() public cloudStatus?: CloudStatus; @property() public cloudStatus?: CloudStatus;
// null means not available // null means not available
@property() public supervisorUpdates?: SupervisorAvailableUpdates[] | null; @property() public updates?: UpdateDescription[] | null;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
@state() private _tip?: string; @state() private _tip?: string;
private _notifyUpdates = false;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-app-layout> <ha-app-layout>
@ -160,50 +155,53 @@ class HaConfigDashboard extends LitElement {
.isWide=${this.isWide} .isWide=${this.isWide}
full-width full-width
> >
${this.supervisorUpdates === undefined ${this.updates === undefined
? // Hide everything until updates loaded ? html`<ha-alert .rtl=${computeRTL(this.hass)}>
html`` ${this.hass.localize(
: html`${this.supervisorUpdates?.length "ui.panel.config.updates.checking_updates"
? html`<ha-card> )}
<ha-config-updates </ha-alert>`
.hass=${this.hass} : this.updates?.length
.narrow=${this.narrow} ? html`<ha-card>
.supervisorUpdates=${this.supervisorUpdates} <ha-config-updates
></ha-config-updates> .hass=${this.hass}
</ha-card>` .narrow=${this.narrow}
: ""} .updates=${this.updates}
<ha-card> ></ha-config-updates>
${this.narrow && this.supervisorUpdates?.length </ha-card>`
? html`<div class="title"> : ""}
${this.hass.localize("panel.config")} <ha-card>
</div>` ${this.narrow && this.updates?.length
: ""} ? html`<div class="title">
${this.cloudStatus && isComponentLoaded(this.hass, "cloud") ${this.hass.localize("panel.config")}
? html` </div>`
<ha-config-navigation : ""}
.hass=${this.hass} ${this.cloudStatus && isComponentLoaded(this.hass, "cloud")
.narrow=${this.narrow} ? html`
.showAdvanced=${this.showAdvanced}
.pages=${[
{
component: "cloud",
path: "/config/cloud",
name: "Home Assistant Cloud",
info: this.cloudStatus,
iconPath: mdiCloudLock,
iconColor: "#3B808E",
},
]}
></ha-config-navigation>
`
: ""}
<ha-config-navigation <ha-config-navigation
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.showAdvanced=${this.showAdvanced} .showAdvanced=${this.showAdvanced}
.pages=${configSections.dashboard} .pages=${[
{
component: "cloud",
path: "/config/cloud",
name: "Home Assistant Cloud",
info: this.cloudStatus,
iconPath: mdiCloudLock,
iconColor: "#3B808E",
},
]}
></ha-config-navigation> ></ha-config-navigation>
</ha-card>`} `
: ""}
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.showAdvanced=${this.showAdvanced}
.pages=${configSections.dashboard}
></ha-config-navigation>
</ha-card>
<div class="tips"> <div class="tips">
<ha-svg-icon .path=${mdiLightbulbOutline}></ha-svg-icon> <ha-svg-icon .path=${mdiLightbulbOutline}></ha-svg-icon>
<span class="tip-word">Tip!</span> <span class="tip-word">Tip!</span>
@ -220,22 +218,6 @@ class HaConfigDashboard extends LitElement {
if (!this._tip && changedProps.has("hass")) { if (!this._tip && changedProps.has("hass")) {
this._tip = randomTip(this.hass); this._tip = randomTip(this.hass);
} }
if (!changedProps.has("supervisorUpdates") || !this._notifyUpdates) {
return;
}
this._notifyUpdates = false;
if (this.supervisorUpdates?.length) {
showToast(this, {
message: this.hass.localize(
"ui.panel.config.updates.updates_refreshed"
),
});
} else {
showToast(this, {
message: this.hass.localize("ui.panel.config.updates.no_new_updates"),
});
}
} }
private _showQuickBar(): void { private _showQuickBar(): void {
@ -248,18 +230,16 @@ class HaConfigDashboard extends LitElement {
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) { private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
if (isComponentLoaded(this.hass, "hassio")) { if (isComponentLoaded(this.hass, "update")) {
this._notifyUpdates = true; fireEvent(this, "ha-refresh-updates");
await refreshSupervisorAvailableUpdates(this.hass);
fireEvent(this, "ha-refresh-supervisor");
return; return;
} }
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.panel.config.updates.check_unavailable.title" "ui.panel.config.updates.update_not_loaded.title"
), ),
text: this.hass.localize( text: this.hass.localize(
"ui.panel.config.updates.check_unavailable.description" "ui.panel.config.updates.update_not_loaded.description"
), ),
warning: true, warning: true,
}); });

View File

@ -1,21 +1,48 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { mdiPackageVariant } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-icon-next";
import "../../../components/ha-logo-svg"; import "../../../components/ha-logo-svg";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { SupervisorAvailableUpdates } from "../../../data/supervisor/root"; import { UpdateDescription } from "../../../data/update";
import { showUpdateDialog } from "../../../dialogs/update-dialog/show-ha-update-dialog";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "../../../components/ha-icon-next"; import { brandsUrl } from "../../../util/brands-url";
export const SUPERVISOR_UPDATE_NAMES = { const sortUpdates = memoizeOne((a: UpdateDescription, b: UpdateDescription) => {
core: "Home Assistant Core", if (a.domain === "hassio" && b.domain === "hassio") {
os: "Home Assistant Operating System", if (a.identifier === "core") {
supervisor: "Home Assistant Supervisor", return -1;
}; }
if (b.identifier === "core") {
return 1;
}
if (a.identifier === "supervisor") {
return -1;
}
if (b.identifier === "supervisor") {
return 1;
}
if (a.identifier === "os") {
return -1;
}
if (b.identifier === "os") {
return 1;
}
}
if (a.domain === "hassio") {
return -1;
}
if (b.domain === "hassio") {
return 1;
}
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
});
@customElement("ha-config-updates") @customElement("ha-config-updates")
class HaConfigUpdates extends LitElement { class HaConfigUpdates extends LitElement {
@ -24,62 +51,62 @@ class HaConfigUpdates extends LitElement {
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) @property({ attribute: false })
public supervisorUpdates?: SupervisorAvailableUpdates[] | null; public updates?: UpdateDescription[] | null;
@state() private _showAll = false; @state() private _showAll = false;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisorUpdates?.length) { if (!this.updates?.length) {
return html``; return html``;
} }
// Make sure the first updates shown are for the Supervisor
const sortedUpdates = this.updates.sort((a, b) => sortUpdates(a, b));
const updates = const updates =
this._showAll || this.supervisorUpdates.length <= 3 this._showAll || sortedUpdates.length <= 3
? this.supervisorUpdates ? sortedUpdates
: this.supervisorUpdates.slice(0, 2); : sortedUpdates.slice(0, 2);
return html` return html`
<div class="title"> <div class="title">
${this.hass.localize("ui.panel.config.updates.title", { ${this.hass.localize("ui.panel.config.updates.title", {
count: this.supervisorUpdates.length, count: sortedUpdates.length,
})} })}
</div> </div>
${updates.map( ${updates.map(
(update) => html` (update) => html`
<a href="/hassio${update.panel_path}"> <paper-icon-item @click=${this._showUpdate} .update=${update}>
<paper-icon-item> <span slot="item-icon" class="icon">
<span slot="item-icon" class="icon"> <img
${update.update_type === "addon" src=${update.icon_url ||
? update.icon brandsUrl({
? html`<img src="/api/hassio${update.icon}" />` domain: update.domain,
: html`<ha-svg-icon type: "icon",
.path=${mdiPackageVariant} useFallback: true,
></ha-svg-icon>` darkOptimized: this.hass.themes?.darkMode,
: html`<ha-logo-svg></ha-logo-svg>`} })}
</span> />
<paper-item-body two-line> </span>
${update.update_type === "addon" <paper-item-body two-line>
? update.name ${update.name}
: SUPERVISOR_UPDATE_NAMES[update.update_type!]} <div secondary>
<div secondary> ${this.hass.localize(
${this.hass.localize( "ui.panel.config.updates.version_available",
"ui.panel.config.updates.version_available", {
{ version_available: update.available_version,
version_available: update.version_latest, }
} )}
)} </div>
</div> </paper-item-body>
</paper-item-body> </paper-icon-item>
${!this.narrow ? html`<ha-icon-next></ha-icon-next>` : ""}
</paper-icon-item>
</a>
` `
)} )}
${!this._showAll && this.supervisorUpdates.length >= 4 ${!this._showAll && this.updates.length >= 4
? html` ? html`
<button class="show-more" @click=${this._showAllClicked}> <button class="show-more" @click=${this._showAllClicked}>
${this.hass.localize("ui.panel.config.updates.more_updates", { ${this.hass.localize("ui.panel.config.updates.more_updates", {
count: this.supervisorUpdates!.length - updates.length, count: this.updates!.length - updates.length,
})} })}
</button> </button>
` `
@ -91,6 +118,14 @@ class HaConfigUpdates extends LitElement {
this._showAll = true; this._showAll = true;
} }
private _showUpdate(ev) {
const update = ev.currentTarget.update as UpdateDescription;
showUpdateDialog(this, {
update,
refreshCallback: () => fireEvent(this, "ha-refresh-updates"),
});
}
static get styles(): CSSResultGroup[] { static get styles(): CSSResultGroup[] {
return [ return [
css` css`
@ -139,6 +174,9 @@ class HaConfigUpdates extends LitElement {
outline: none; outline: none;
text-decoration: underline; text-decoration: underline;
} }
paper-icon-item {
cursor: pointer;
}
`, `,
]; ];
} }

View File

@ -27,20 +27,18 @@ import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { listenMediaQuery } from "../../common/dom/media_query"; import { listenMediaQuery } from "../../common/dom/media_query";
import { CloudStatus, fetchCloudStatus } from "../../data/cloud"; import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
import { import { fetchUpdateInfo, UpdateDescription } from "../../data/update";
fetchSupervisorAvailableUpdates,
SupervisorAvailableUpdates,
} from "../../data/supervisor/root";
import "../../layouts/hass-loading-screen"; import "../../layouts/hass-loading-screen";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import { PageNavigation } from "../../layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../types"; import { HomeAssistant, Route } from "../../types";
import { showToast } from "../../util/toast";
declare global { declare global {
// for fire event // for fire event
interface HASSDomEvents { interface HASSDomEvents {
"ha-refresh-cloud-status": undefined; "ha-refresh-cloud-status": undefined;
"ha-refresh-supervisor": undefined; "ha-refresh-updates": undefined;
} }
} }
@ -407,7 +405,7 @@ class HaPanelConfig extends HassRouterPage {
@state() private _cloudStatus?: CloudStatus; @state() private _cloudStatus?: CloudStatus;
@state() private _supervisorUpdates?: SupervisorAvailableUpdates[] | null; @state() private _updates?: UpdateDescription[] | null;
private _listeners: Array<() => void> = []; private _listeners: Array<() => void> = [];
@ -443,18 +441,18 @@ class HaPanelConfig extends HassRouterPage {
} }
}); });
} }
if (isComponentLoaded(this.hass, "hassio")) { if (isComponentLoaded(this.hass, "update")) {
this._loadSupervisorUpdates(); this._loadUpdates();
this.addEventListener("ha-refresh-supervisor", () => { this.addEventListener("ha-refresh-updates", () => {
this._loadSupervisorUpdates(); this._loadUpdates();
}); });
this.addEventListener("connection-status", (ev) => { this.addEventListener("connection-status", (ev) => {
if (ev.detail === "connected") { if (ev.detail === "connected") {
this._loadSupervisorUpdates(); this._loadUpdates();
} }
}); });
} else { } else {
this._supervisorUpdates = null; this._updates = null;
} }
this.addEventListener("ha-refresh-cloud-status", () => this.addEventListener("ha-refresh-cloud-status", () =>
this._updateCloudStatus() this._updateCloudStatus()
@ -486,7 +484,7 @@ class HaPanelConfig extends HassRouterPage {
isWide, isWide,
narrow: this.narrow, narrow: this.narrow,
cloudStatus: this._cloudStatus, cloudStatus: this._cloudStatus,
supervisorUpdates: this._supervisorUpdates, updates: this._updates,
}); });
} else { } else {
el.route = this.routeTail; el.route = this.routeTail;
@ -495,7 +493,7 @@ class HaPanelConfig extends HassRouterPage {
el.isWide = isWide; el.isWide = isWide;
el.narrow = this.narrow; el.narrow = this.narrow;
el.cloudStatus = this._cloudStatus; el.cloudStatus = this._cloudStatus;
el.supervisorUpdates = this._supervisorUpdates; el.updates = this._updates;
} }
} }
@ -514,13 +512,33 @@ class HaPanelConfig extends HassRouterPage {
} }
} }
private async _loadSupervisorUpdates(): Promise<void> { private async _loadUpdates(): Promise<void> {
const _showToast = this._updates !== undefined;
if (_showToast) {
showToast(this, {
message: this.hass.localize("ui.panel.config.updates.checking_updates"),
});
}
try { try {
this._supervisorUpdates = await fetchSupervisorAvailableUpdates( this._updates = await fetchUpdateInfo(this.hass);
this.hass
);
} catch (err) { } catch (err) {
this._supervisorUpdates = null; this._updates = null;
}
if (_showToast) {
if (this._updates?.length) {
showToast(this, {
message: this.hass.localize(
"ui.panel.config.updates.updates_refreshed"
),
});
} else {
showToast(this, {
message: this.hass.localize("ui.panel.config.updates.no_new_updates"),
});
}
} }
} }
} }

View File

@ -1047,18 +1047,27 @@
"learn_more": "Learn more" "learn_more": "Learn more"
}, },
"updates": { "updates": {
"check_unavailable": { "update_not_loaded": {
"title": "Unable to check for updates", "title": "Unable to check for updates",
"description": "You need to run the Home Assistant operating system to be able to check and install updates from the Home Assistant user interface." "description": "You need to enable the update integrtion to be able to check and install updates from the Home Assistant user interface."
}, },
"check_updates": "Check for updates", "check_updates": "Check for updates",
"checking_updates": "Checking for available updates",
"no_new_updates": "No new updates found", "no_new_updates": "No new updates found",
"updates_refreshed": "Updates refreshed", "updates_refreshed": "Updates refreshed",
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}", "title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
"unable_to_fetch": "Unable to load updates", "unable_to_fetch": "Unable to load updates",
"version_available": "Version {version_available} is available", "version_available": "Version {version_available} is available",
"more_updates": "+{count} updates", "more_updates": "+{count} updates",
"show": "show" "show": "show",
"dialog": {
"title": "[%key:supervisor::update_available::update_name%]",
"create_backup": "[%key:supervisor::update_available::create_backup%]",
"open_changelog": "Open changelog",
"updating": "[%key:supervisor::update_available::updating%]",
"update": "[%key:supervisor::common::update%]",
"description": "[%key:supervisor::update_available::description%]"
}
}, },
"areas": { "areas": {
"caption": "Areas", "caption": "Areas",