mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-02 05:57:54 +00:00
Use the update integration to provide updates
This commit is contained in:
parent
604b79696e
commit
9ea8e13c87
@ -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
37
src/data/update.ts
Normal 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 });
|
211
src/dialogs/update-dialog/ha-update-dialog.ts
Normal file
211
src/dialogs/update-dialog/ha-update-dialog.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
18
src/dialogs/update-dialog/show-ha-update-dialog.ts
Normal file
18
src/dialogs/update-dialog/show-ha-update-dialog.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
@ -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,
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -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"),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user