mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 11:16:35 +00:00
Render update card on add-on page (#10681)
This commit is contained in:
parent
f833701e7c
commit
ed291b57d0
@ -4,7 +4,7 @@ import "../../../../src/components/ha-circular-progress";
|
|||||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||||
import { haStyle } from "../../../../src/resources/styles";
|
import { haStyle } from "../../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant, Route } from "../../../../src/types";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
import { hassioStyle } from "../../resources/hassio-style";
|
||||||
import "./hassio-addon-info";
|
import "./hassio-addon-info";
|
||||||
|
|
||||||
@ -12,6 +12,8 @@ import "./hassio-addon-info";
|
|||||||
class HassioAddonInfoDashboard extends LitElement {
|
class HassioAddonInfoDashboard extends LitElement {
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||||
@ -27,6 +29,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<hassio-addon-info
|
<hassio-addon-info
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
|
.route=${this.route}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this.supervisor}
|
.supervisor=${this.supervisor}
|
||||||
.addon=${this.addon}
|
.addon=${this.addon}
|
||||||
|
@ -62,12 +62,13 @@ import {
|
|||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../src/resources/styles";
|
import { haStyle } from "../../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant, Route } from "../../../../src/types";
|
||||||
import { bytesToString } from "../../../../src/util/bytes-to-string";
|
import { bytesToString } from "../../../../src/util/bytes-to-string";
|
||||||
import "../../components/hassio-card-content";
|
import "../../components/hassio-card-content";
|
||||||
import "../../components/supervisor-metric";
|
import "../../components/supervisor-metric";
|
||||||
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
import { hassioStyle } from "../../resources/hassio-style";
|
||||||
|
import "../../update-available/update-available-card";
|
||||||
import { addonArchIsSupported, extractChangelog } from "../../util/addon";
|
import { addonArchIsSupported, extractChangelog } from "../../util/addon";
|
||||||
|
|
||||||
const STAGE_ICON = {
|
const STAGE_ICON = {
|
||||||
@ -89,6 +90,8 @@ const RATING_ICON = {
|
|||||||
class HassioAddonInfo extends LitElement {
|
class HassioAddonInfo extends LitElement {
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||||
@ -125,23 +128,12 @@ class HassioAddonInfo extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
${this.addon.update_available
|
${this.addon.update_available
|
||||||
? html`
|
? html`
|
||||||
<ha-alert
|
<update-available-card
|
||||||
.title=${this.supervisor.localize("common.update_available", {
|
.hass=${this.hass}
|
||||||
count: 1,
|
.narrow=${this.narrow}
|
||||||
})}
|
.supervisor=${this.supervisor}
|
||||||
>
|
.addonSlug=${this.addon.slug}
|
||||||
${this.supervisor.localize(
|
></update-available-card>
|
||||||
"addon.dashboard.new_update_available",
|
|
||||||
{ name: this.addon.name, version: this.addon.version_latest }
|
|
||||||
)}
|
|
||||||
<a
|
|
||||||
href="/hassio/update-available/${this.addon.slug}"
|
|
||||||
slot="action"
|
|
||||||
>
|
|
||||||
<mwc-button .label=${this.supervisor.localize("common.review")}>
|
|
||||||
</mwc-button>
|
|
||||||
</a>
|
|
||||||
</ha-alert>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${!this.addon.protected
|
${!this.addon.protected
|
||||||
@ -1171,6 +1163,10 @@ class HassioAddonInfo extends LitElement {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update-available-card {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
ha-chip {
|
ha-chip {
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
|
398
hassio/src/update-available/update-available-card.ts
Normal file
398
hassio/src/update-available/update-available-card.ts
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
import "../../../src/common/search/search-input";
|
||||||
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
|
import "../../../src/components/ha-alert";
|
||||||
|
import "../../../src/components/ha-button-menu";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-checkbox";
|
||||||
|
import "../../../src/components/ha-expansion-panel";
|
||||||
|
import "../../../src/components/ha-formfield";
|
||||||
|
import "../../../src/components/ha-icon-button";
|
||||||
|
import "../../../src/components/ha-markdown";
|
||||||
|
import "../../../src/components/ha-settings-row";
|
||||||
|
import "../../../src/components/ha-svg-icon";
|
||||||
|
import "../../../src/components/ha-switch";
|
||||||
|
import {
|
||||||
|
fetchHassioAddonChangelog,
|
||||||
|
fetchHassioAddonInfo,
|
||||||
|
HassioAddonDetails,
|
||||||
|
updateHassioAddon,
|
||||||
|
} from "../../../src/data/hassio/addon";
|
||||||
|
import {
|
||||||
|
createHassioPartialBackup,
|
||||||
|
HassioPartialBackupCreateParams,
|
||||||
|
} from "../../../src/data/hassio/backup";
|
||||||
|
import {
|
||||||
|
extractApiErrorMessage,
|
||||||
|
ignoreSupervisorError,
|
||||||
|
} from "../../../src/data/hassio/common";
|
||||||
|
import { updateOS } from "../../../src/data/hassio/host";
|
||||||
|
import { updateSupervisor } from "../../../src/data/hassio/supervisor";
|
||||||
|
import { updateCore } from "../../../src/data/supervisor/core";
|
||||||
|
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||||
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
|
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
|
import "../../../src/layouts/hass-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 { documentationUrl } from "../../../src/util/documentation-url";
|
||||||
|
import { addonArchIsSupported, extractChangelog } from "../util/addon";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"update-complete": undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||||
|
|
||||||
|
const changelogUrl = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: updateType,
|
||||||
|
version: string
|
||||||
|
): string | undefined => {
|
||||||
|
if (entry === "addon") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (entry === "core") {
|
||||||
|
return version?.includes("dev")
|
||||||
|
? "https://github.com/home-assistant/core/commits/dev"
|
||||||
|
: documentationUrl(hass, "/latest-release-notes/");
|
||||||
|
}
|
||||||
|
if (entry === "os") {
|
||||||
|
return version?.includes("dev")
|
||||||
|
? "https://github.com/home-assistant/operating-system/commits/dev"
|
||||||
|
: `https://github.com/home-assistant/operating-system/releases/tag/${version}`;
|
||||||
|
}
|
||||||
|
if (entry === "supervisor") {
|
||||||
|
return version?.includes("dev")
|
||||||
|
? "https://github.com/home-assistant/supervisor/commits/main"
|
||||||
|
: `https://github.com/home-assistant/supervisor/releases/tag/${version}`;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("update-available-card")
|
||||||
|
class UpdateAvailableCard extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public addonSlug?: string;
|
||||||
|
|
||||||
|
@state() private _updateType?: updateType;
|
||||||
|
|
||||||
|
@state() private _changelogContent?: string;
|
||||||
|
|
||||||
|
@state() private _addonInfo?: HassioAddonDetails;
|
||||||
|
|
||||||
|
@state() private _action: "backup" | "update" | null = null;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
private _addonStoreInfo = memoizeOne(
|
||||||
|
(slug: string, storeAddons: StoreAddon[]) =>
|
||||||
|
storeAddons.find((addon) => addon.slug === slug)
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (
|
||||||
|
!this._updateType ||
|
||||||
|
(this._updateType === "addon" && !this._addonInfo)
|
||||||
|
) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changelog = changelogUrl(this.hass, this._updateType, this._version);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card
|
||||||
|
.header=${this.supervisor.localize("update_available.update_name", {
|
||||||
|
name: this._name,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
${this._error
|
||||||
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
${this._action === null
|
||||||
|
? html`
|
||||||
|
${this._changelogContent
|
||||||
|
? html`
|
||||||
|
<ha-expansion-panel header="Changelog" outlined>
|
||||||
|
<ha-markdown .content=${this._changelogContent}>
|
||||||
|
</ha-markdown>
|
||||||
|
</ha-expansion-panel>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div class="versions">
|
||||||
|
<p>
|
||||||
|
${this.supervisor.localize("update_available.description", {
|
||||||
|
name: this._name,
|
||||||
|
version: this._version,
|
||||||
|
newest_version: this._version_latest,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
${["core", "addon"].includes(this._updateType)
|
||||||
|
? html`
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.supervisor.localize(
|
||||||
|
"update_available.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._action === "update"
|
||||||
|
? this.supervisor.localize("update_available.updating", {
|
||||||
|
name: this._name,
|
||||||
|
version: this._version_latest,
|
||||||
|
})
|
||||||
|
: this.supervisor.localize(
|
||||||
|
"update_available.creating_backup",
|
||||||
|
{ name: this._name }
|
||||||
|
)}
|
||||||
|
</p>`}
|
||||||
|
</div>
|
||||||
|
${this._action === null
|
||||||
|
? html`
|
||||||
|
<div class="card-actions">
|
||||||
|
${changelog
|
||||||
|
? html`<a .href=${changelog} target="_blank" rel="noreferrer">
|
||||||
|
<mwc-button
|
||||||
|
.label=${this.supervisor.localize(
|
||||||
|
"update_available.open_release_notes"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</mwc-button>
|
||||||
|
</a>`
|
||||||
|
: ""}
|
||||||
|
<span></span>
|
||||||
|
<ha-progress-button
|
||||||
|
.disabled=${!this._version ||
|
||||||
|
this._error !== undefined ||
|
||||||
|
(this._shouldCreateBackup &&
|
||||||
|
this.supervisor.info.state !== "running")}
|
||||||
|
@click=${this._update}
|
||||||
|
raised
|
||||||
|
>
|
||||||
|
${this.supervisor.localize("common.update")}
|
||||||
|
</ha-progress-button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
const pathPart = this.route?.path.substring(1, this.route.path.length);
|
||||||
|
const updateType = ["core", "os", "supervisor"].includes(pathPart)
|
||||||
|
? pathPart
|
||||||
|
: "addon";
|
||||||
|
this._updateType = updateType as updateType;
|
||||||
|
|
||||||
|
if (updateType === "addon") {
|
||||||
|
if (!this.addonSlug) {
|
||||||
|
this.addonSlug = pathPart;
|
||||||
|
}
|
||||||
|
this._loadAddonData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get _shouldCreateBackup(): boolean {
|
||||||
|
return this.shadowRoot?.querySelector("ha-checkbox")?.checked || true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _version(): string {
|
||||||
|
return this._updateType
|
||||||
|
? this._updateType === "addon"
|
||||||
|
? this._addonInfo!.version
|
||||||
|
: this.supervisor[this._updateType]?.version || ""
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
get _version_latest(): string {
|
||||||
|
return this._updateType
|
||||||
|
? this._updateType === "addon"
|
||||||
|
? this._addonInfo!.version_latest
|
||||||
|
: this.supervisor[this._updateType]?.version_latest || ""
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
get _name(): string {
|
||||||
|
return this._updateType
|
||||||
|
? this._updateType === "addon"
|
||||||
|
? this._addonInfo!.name
|
||||||
|
: SUPERVISOR_UPDATE_NAMES[this._updateType]
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadAddonData() {
|
||||||
|
try {
|
||||||
|
this._addonInfo = await fetchHassioAddonInfo(this.hass, this.addonSlug!);
|
||||||
|
} catch (err) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
title: this._updateType,
|
||||||
|
text: extractApiErrorMessage(err),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const addonStoreInfo =
|
||||||
|
!this._addonInfo.detached && !this._addonInfo.available
|
||||||
|
? this._addonStoreInfo(
|
||||||
|
this._addonInfo.slug,
|
||||||
|
this.supervisor.store.addons
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (this._addonInfo.changelog) {
|
||||||
|
try {
|
||||||
|
const content = await fetchHassioAddonChangelog(
|
||||||
|
this.hass,
|
||||||
|
this._updateType!
|
||||||
|
);
|
||||||
|
this._changelogContent = extractChangelog(this._addonInfo, content);
|
||||||
|
} catch (err) {
|
||||||
|
this._error = extractApiErrorMessage(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._addonInfo.available && addonStoreInfo) {
|
||||||
|
if (
|
||||||
|
!addonArchIsSupported(
|
||||||
|
this.supervisor.info.supported_arch,
|
||||||
|
this._addonInfo.arch
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this._error = this.supervisor.localize(
|
||||||
|
"addon.dashboard.not_available_arch"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._error = this.supervisor.localize(
|
||||||
|
"addon.dashboard.not_available_version",
|
||||||
|
{
|
||||||
|
core_version_installed: this.supervisor.core.version,
|
||||||
|
core_version_needed: addonStoreInfo.homeassistant,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _update() {
|
||||||
|
if (this._shouldCreateBackup) {
|
||||||
|
let backupArgs: HassioPartialBackupCreateParams;
|
||||||
|
if (this._updateType === "addon") {
|
||||||
|
backupArgs = {
|
||||||
|
name: `addon_${this._updateType}_${this._addonInfo?.version}`,
|
||||||
|
addons: [this._updateType!],
|
||||||
|
homeassistant: false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
backupArgs = {
|
||||||
|
name: `${this._updateType}_${this._addonInfo?.version}`,
|
||||||
|
folders: ["homeassistant"],
|
||||||
|
homeassistant: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this._action = "backup";
|
||||||
|
try {
|
||||||
|
await createHassioPartialBackup(this.hass, backupArgs);
|
||||||
|
} catch (err: any) {
|
||||||
|
this._error = extractApiErrorMessage(err);
|
||||||
|
this._action = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._action = "update";
|
||||||
|
try {
|
||||||
|
if (this._updateType === "addon") {
|
||||||
|
await updateHassioAddon(this.hass, this._updateType!);
|
||||||
|
} else if (this._updateType === "core") {
|
||||||
|
await updateCore(this.hass);
|
||||||
|
} else if (this._updateType === "os") {
|
||||||
|
await updateOS(this.hass);
|
||||||
|
} else if (this._updateType === "supervisor") {
|
||||||
|
await updateSupervisor(this.hass);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||||
|
this._error = extractApiErrorMessage(err);
|
||||||
|
this._action = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fireEvent(this, "update-complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-top: none;
|
||||||
|
padding: 0 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-circular-progress {
|
||||||
|
display: block;
|
||||||
|
margin: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-markdown {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"update-available-card": UpdateAvailableCard;
|
||||||
|
}
|
||||||
|
}
|
@ -1,78 +1,11 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import {
|
import { customElement, property } from "lit/decorators";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { property, state } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import "../../../src/common/search/search-input";
|
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
|
||||||
import "../../../src/components/ha-alert";
|
|
||||||
import "../../../src/components/ha-button-menu";
|
|
||||||
import "../../../src/components/ha-card";
|
|
||||||
import "../../../src/components/ha-checkbox";
|
|
||||||
import "../../../src/components/ha-expansion-panel";
|
|
||||||
import "../../../src/components/ha-formfield";
|
|
||||||
import "../../../src/components/ha-icon-button";
|
|
||||||
import "../../../src/components/ha-markdown";
|
|
||||||
import "../../../src/components/ha-settings-row";
|
|
||||||
import "../../../src/components/ha-svg-icon";
|
|
||||||
import "../../../src/components/ha-switch";
|
|
||||||
import {
|
|
||||||
fetchHassioAddonChangelog,
|
|
||||||
fetchHassioAddonInfo,
|
|
||||||
HassioAddonDetails,
|
|
||||||
updateHassioAddon,
|
|
||||||
} from "../../../src/data/hassio/addon";
|
|
||||||
import {
|
|
||||||
createHassioPartialBackup,
|
|
||||||
HassioPartialBackupCreateParams,
|
|
||||||
} from "../../../src/data/hassio/backup";
|
|
||||||
import {
|
|
||||||
extractApiErrorMessage,
|
|
||||||
ignoreSupervisorError,
|
|
||||||
} from "../../../src/data/hassio/common";
|
|
||||||
import { updateOS } from "../../../src/data/hassio/host";
|
|
||||||
import { updateSupervisor } from "../../../src/data/hassio/supervisor";
|
|
||||||
import { updateCore } from "../../../src/data/supervisor/core";
|
|
||||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
|
||||||
import "../../../src/layouts/hass-loading-screen";
|
|
||||||
import "../../../src/layouts/hass-subpage";
|
import "../../../src/layouts/hass-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 { documentationUrl } from "../../../src/util/documentation-url";
|
import "./update-available-card";
|
||||||
import { addonArchIsSupported, extractChangelog } from "../util/addon";
|
|
||||||
|
|
||||||
const changelogUrl = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entry: string,
|
|
||||||
version: string
|
|
||||||
): string | undefined => {
|
|
||||||
if (entry === "core") {
|
|
||||||
return version?.includes("dev")
|
|
||||||
? "https://github.com/home-assistant/core/commits/dev"
|
|
||||||
: documentationUrl(hass, "/latest-release-notes/");
|
|
||||||
}
|
|
||||||
if (entry === "os") {
|
|
||||||
return version?.includes("dev")
|
|
||||||
? "https://github.com/home-assistant/operating-system/commits/dev"
|
|
||||||
: `https://github.com/home-assistant/operating-system/releases/tag/${version}`;
|
|
||||||
}
|
|
||||||
if (entry === "supervisor") {
|
|
||||||
return version?.includes("dev")
|
|
||||||
? "https://github.com/home-assistant/supervisor/commits/main"
|
|
||||||
: `https://github.com/home-assistant/supervisor/releases/tag/${version}`;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
@customElement("update-available-dashboard")
|
||||||
class UpdateAvailableDashboard extends LitElement {
|
class UpdateAvailableDashboard extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@ -82,258 +15,25 @@ class UpdateAvailableDashboard extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@state() private _updateEntry?: string;
|
|
||||||
|
|
||||||
@state() private _changelogContent?: string;
|
|
||||||
|
|
||||||
@state() private _addonInfo?: HassioAddonDetails;
|
|
||||||
|
|
||||||
@state() private _createBackup = true;
|
|
||||||
|
|
||||||
@state() private _action: "backup" | "update" | null = null;
|
|
||||||
|
|
||||||
@state() private _error?: string;
|
|
||||||
|
|
||||||
private _isAddon = false;
|
|
||||||
|
|
||||||
private _addonStoreInfo = memoizeOne(
|
|
||||||
(slug: string, storeAddons: StoreAddon[]) =>
|
|
||||||
storeAddons.find((addon) => addon.slug === slug)
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._updateEntry) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
const name =
|
|
||||||
// @ts-ignore
|
|
||||||
this._addonInfo?.name || SUPERVISOR_UPDATE_NAMES[this._updateEntry];
|
|
||||||
const changelog = !this._isAddon
|
|
||||||
? changelogUrl(
|
|
||||||
this.hass,
|
|
||||||
this._updateEntry,
|
|
||||||
this.supervisor[this._updateEntry]?.version
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage
|
<hass-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
>
|
>
|
||||||
<ha-card
|
<update-available-card
|
||||||
.header=${this.supervisor.localize("update_available.update_name", {
|
.hass=${this.hass}
|
||||||
name,
|
.supervisor=${this.supervisor}
|
||||||
})}
|
.route=${this.route}
|
||||||
>
|
.narrow=${this.narrow}
|
||||||
<div class="card-content">
|
@update-complete=${this._updateComplete}
|
||||||
${this._error
|
></update-available-card>
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
|
||||||
: ""}
|
|
||||||
${this._action === null
|
|
||||||
? html`
|
|
||||||
${this._changelogContent
|
|
||||||
? html`
|
|
||||||
<ha-expansion-panel header="Changelog" outlined>
|
|
||||||
<ha-markdown .content=${this._changelogContent}>
|
|
||||||
</ha-markdown>
|
|
||||||
</ha-expansion-panel>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<div class="versions">
|
|
||||||
<p>
|
|
||||||
${this.supervisor.localize(
|
|
||||||
"update_available.description",
|
|
||||||
{
|
|
||||||
version:
|
|
||||||
this._addonInfo?.version ||
|
|
||||||
this.supervisor[this._updateEntry]?.version,
|
|
||||||
newest_version:
|
|
||||||
this._addonInfo?.version_latest ||
|
|
||||||
this.supervisor[this._updateEntry]?.version_latest,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
${!["os", "supervisor"].includes(this._updateEntry)
|
|
||||||
? html`
|
|
||||||
<ha-formfield
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"update_available.create_backup"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-checkbox
|
|
||||||
.checked=${this._createBackup}
|
|
||||||
@click=${this._toggleBackup}
|
|
||||||
>
|
|
||||||
</ha-checkbox>
|
|
||||||
</ha-formfield>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
`
|
|
||||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
|
||||||
</ha-circular-progress>
|
|
||||||
<p class="progress-text">
|
|
||||||
${this._action === "update"
|
|
||||||
? this.supervisor.localize("update_available.updating", {
|
|
||||||
name,
|
|
||||||
version:
|
|
||||||
this._addonInfo?.version_latest ||
|
|
||||||
this.supervisor[this._updateEntry]?.version_latest,
|
|
||||||
})
|
|
||||||
: this.supervisor.localize(
|
|
||||||
"update_available.creating_backup",
|
|
||||||
{ name }
|
|
||||||
)}
|
|
||||||
</p>`}
|
|
||||||
</div>
|
|
||||||
${this._action === null
|
|
||||||
? html`
|
|
||||||
<div class="card-actions">
|
|
||||||
${changelog
|
|
||||||
? html`<a
|
|
||||||
.href=${changelog}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"update_available.open_release_notes"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</mwc-button>
|
|
||||||
</a>`
|
|
||||||
: ""}
|
|
||||||
<span></span>
|
|
||||||
<ha-progress-button
|
|
||||||
.disabled=${this._error !== undefined}
|
|
||||||
@click=${this._update}
|
|
||||||
raised
|
|
||||||
>
|
|
||||||
${this.supervisor.localize("common.update")}
|
|
||||||
</ha-progress-button>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</ha-card>
|
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
private _updateComplete() {
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this._updateEntry = this.route.path.substring(1, this.route.path.length);
|
|
||||||
this._isAddon = !["core", "os", "supervisor"].includes(this._updateEntry);
|
|
||||||
if (this._isAddon) {
|
|
||||||
this._loadAddonData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadAddonData() {
|
|
||||||
try {
|
|
||||||
this._addonInfo = await fetchHassioAddonInfo(
|
|
||||||
this.hass,
|
|
||||||
this._updateEntry!
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
title: this._updateEntry,
|
|
||||||
text: extractApiErrorMessage(err),
|
|
||||||
confirm: () => history.back(),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const addonStoreInfo =
|
|
||||||
!this._addonInfo.detached && !this._addonInfo.available
|
|
||||||
? this._addonStoreInfo(
|
|
||||||
this._addonInfo.slug,
|
|
||||||
this.supervisor.store.addons
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (this._addonInfo.changelog) {
|
|
||||||
try {
|
|
||||||
const content = await fetchHassioAddonChangelog(
|
|
||||||
this.hass,
|
|
||||||
this._updateEntry!
|
|
||||||
);
|
|
||||||
this._changelogContent = extractChangelog(this._addonInfo, content);
|
|
||||||
} catch (err) {
|
|
||||||
this._error = extractApiErrorMessage(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._addonInfo.available && addonStoreInfo) {
|
|
||||||
if (
|
|
||||||
!addonArchIsSupported(
|
|
||||||
this.supervisor.info.supported_arch,
|
|
||||||
this._addonInfo.arch
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this._error = this.supervisor.localize(
|
|
||||||
"addon.dashboard.not_available_arch"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this._error = this.supervisor.localize(
|
|
||||||
"addon.dashboard.not_available_version",
|
|
||||||
{
|
|
||||||
core_version_installed: this.supervisor.core.version,
|
|
||||||
core_version_needed: addonStoreInfo.homeassistant,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _toggleBackup() {
|
|
||||||
this._createBackup = !this._createBackup;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _update() {
|
|
||||||
if (this._createBackup) {
|
|
||||||
let backupArgs: HassioPartialBackupCreateParams;
|
|
||||||
if (this._isAddon) {
|
|
||||||
backupArgs = {
|
|
||||||
name: `addon_${this._updateEntry}_${this._addonInfo?.version}`,
|
|
||||||
addons: [this._updateEntry!],
|
|
||||||
homeassistant: false,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
backupArgs = {
|
|
||||||
name: `${this._updateEntry}_${this._addonInfo?.version}`,
|
|
||||||
folders: ["homeassistant"],
|
|
||||||
homeassistant: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this._action = "backup";
|
|
||||||
try {
|
|
||||||
await createHassioPartialBackup(this.hass, backupArgs);
|
|
||||||
} catch (err: any) {
|
|
||||||
this._error = extractApiErrorMessage(err);
|
|
||||||
this._action = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._action = "update";
|
|
||||||
try {
|
|
||||||
if (this._isAddon) {
|
|
||||||
await updateHassioAddon(this.hass, this._updateEntry!);
|
|
||||||
} else if (this._updateEntry === "core") {
|
|
||||||
await updateCore(this.hass);
|
|
||||||
} else if (this._updateEntry === "os") {
|
|
||||||
await updateOS(this.hass);
|
|
||||||
} else if (this._updateEntry === "supervisor") {
|
|
||||||
await updateSupervisor(this.hass);
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
|
||||||
this._error = extractApiErrorMessage(err);
|
|
||||||
this._action = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
history.back();
|
history.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,34 +43,17 @@ class UpdateAvailableDashboard extends LitElement {
|
|||||||
--app-header-background-color: var(--primary-background-color);
|
--app-header-background-color: var(--primary-background-color);
|
||||||
--app-header-text-color: var(--sidebar-text-color);
|
--app-header-text-color: var(--sidebar-text-color);
|
||||||
}
|
}
|
||||||
ha-card {
|
update-available-card {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
ha-settings-row {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-circular-progress {
|
|
||||||
display: block;
|
|
||||||
margin: 32px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-text {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("update-available-dashboard", UpdateAvailableDashboard);
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"update-available-dashboard": UpdateAvailableDashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user