mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-24 20:55:49 +00:00
503 lines
15 KiB
TypeScript
503 lines
15 KiB
TypeScript
import "@material/mwc-linear-progress/mwc-linear-progress";
|
|
import { css, html, LitElement, nothing } from "lit";
|
|
import { customElement, property, state } from "lit/decorators";
|
|
import { BINARY_STATE_OFF } from "../../../common/const";
|
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
|
import "../../../components/ha-alert";
|
|
import "../../../components/ha-button";
|
|
import "../../../components/ha-checkbox";
|
|
import "../../../components/ha-circular-progress";
|
|
import "../../../components/ha-faded";
|
|
import "../../../components/ha-formfield";
|
|
import "../../../components/ha-markdown";
|
|
import "../../../components/ha-md-list";
|
|
import "../../../components/ha-md-list-item";
|
|
import "../../../components/ha-switch";
|
|
import type { HaSwitch } from "../../../components/ha-switch";
|
|
import type { BackupConfig } from "../../../data/backup";
|
|
import { fetchBackupConfig } from "../../../data/backup";
|
|
import { isUnavailableState } from "../../../data/entity";
|
|
import type { EntitySources } from "../../../data/entity_sources";
|
|
import { fetchEntitySourcesWithCache } from "../../../data/entity_sources";
|
|
import type { UpdateEntity } from "../../../data/update";
|
|
import {
|
|
getUpdateType,
|
|
UpdateEntityFeature,
|
|
updateIsInstalling,
|
|
updateReleaseNotes,
|
|
} from "../../../data/update";
|
|
import type { HomeAssistant } from "../../../types";
|
|
import { showAlertDialog } from "../../generic/show-dialog-box";
|
|
|
|
@customElement("more-info-update")
|
|
class MoreInfoUpdate extends LitElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public stateObj?: UpdateEntity;
|
|
|
|
@state() private _releaseNotes?: string | null;
|
|
|
|
@state() private _error?: string;
|
|
|
|
@state() private _markdownLoading = true;
|
|
|
|
@state() private _backupConfig?: BackupConfig;
|
|
|
|
@state() private _entitySources?: EntitySources;
|
|
|
|
private async _fetchBackupConfig() {
|
|
const { config } = await fetchBackupConfig(this.hass);
|
|
this._backupConfig = config;
|
|
}
|
|
|
|
private async _fetchEntitySources() {
|
|
this._entitySources = await fetchEntitySourcesWithCache(this.hass);
|
|
}
|
|
|
|
private _computeCreateBackupTexts():
|
|
| { title: string; description?: string }
|
|
| undefined {
|
|
if (
|
|
!this.stateObj ||
|
|
!supportsFeature(this.stateObj, UpdateEntityFeature.BACKUP)
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const updateType = this._entitySources
|
|
? getUpdateType(this.stateObj, this._entitySources)
|
|
: "generic";
|
|
|
|
// Automatic or manual for Home Assistant update
|
|
if (updateType === "home_assistant") {
|
|
const isBackupConfigValid =
|
|
!!this._backupConfig &&
|
|
!!this._backupConfig.create_backup.password &&
|
|
this._backupConfig.create_backup.agent_ids.length > 0;
|
|
|
|
if (!isBackupConfigValid) {
|
|
return {
|
|
title: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.manual"
|
|
),
|
|
description: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.manual_description"
|
|
),
|
|
};
|
|
}
|
|
|
|
const lastAutomaticBackupDate = this._backupConfig
|
|
?.last_completed_automatic_backup
|
|
? new Date(this._backupConfig?.last_completed_automatic_backup)
|
|
: null;
|
|
const now = new Date();
|
|
|
|
return {
|
|
title: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.automatic"
|
|
),
|
|
description: lastAutomaticBackupDate
|
|
? this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.automatic_description_last",
|
|
{
|
|
relative_time: relativeTime(
|
|
lastAutomaticBackupDate,
|
|
this.hass.locale,
|
|
now,
|
|
true
|
|
),
|
|
}
|
|
)
|
|
: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.automatic_description_none"
|
|
),
|
|
};
|
|
}
|
|
|
|
// Addon backup
|
|
if (updateType === "addon") {
|
|
const version = this.stateObj.attributes.installed_version;
|
|
return {
|
|
title: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.addon"
|
|
),
|
|
description: version
|
|
? this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.addon_description",
|
|
{ version: version }
|
|
)
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
// Fallback to generic UI
|
|
return {
|
|
title: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.create_backup.generic"
|
|
),
|
|
};
|
|
}
|
|
|
|
protected render() {
|
|
if (
|
|
!this.hass ||
|
|
!this.stateObj ||
|
|
isUnavailableState(this.stateObj.state)
|
|
) {
|
|
return nothing;
|
|
}
|
|
|
|
const skippedVersion =
|
|
this.stateObj.attributes.latest_version &&
|
|
this.stateObj.attributes.skipped_version ===
|
|
this.stateObj.attributes.latest_version;
|
|
|
|
const createBackupTexts = this._computeCreateBackupTexts();
|
|
|
|
return html`
|
|
<div class="content">
|
|
<div class="summary">
|
|
${this.stateObj.attributes.in_progress
|
|
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
|
|
this.stateObj.attributes.update_percentage !== null
|
|
? html`<mwc-linear-progress
|
|
.progress=${this.stateObj.attributes.update_percentage / 100}
|
|
buffer=""
|
|
></mwc-linear-progress>`
|
|
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
|
|
: nothing}
|
|
<h3>${this.stateObj.attributes.title}</h3>
|
|
${this._error
|
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
|
: nothing}
|
|
<div class="row">
|
|
<div class="key">
|
|
${this.hass.formatEntityAttributeName(
|
|
this.stateObj,
|
|
"installed_version"
|
|
)}
|
|
</div>
|
|
<div class="value">
|
|
${this.stateObj.attributes.installed_version ??
|
|
this.hass.localize("state.default.unavailable")}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="key">
|
|
${this.hass.formatEntityAttributeName(
|
|
this.stateObj,
|
|
"latest_version"
|
|
)}
|
|
</div>
|
|
<div class="value">
|
|
${this.stateObj.attributes.latest_version ??
|
|
this.hass.localize("state.default.unavailable")}
|
|
</div>
|
|
</div>
|
|
|
|
${this.stateObj.attributes.release_url
|
|
? html`<div class="row">
|
|
<div class="key">
|
|
<a
|
|
href=${this.stateObj.attributes.release_url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
${this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.release_announcement"
|
|
)}
|
|
</a>
|
|
</div>
|
|
</div>`
|
|
: nothing}
|
|
</div>
|
|
${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) &&
|
|
!this._error
|
|
? this._releaseNotes === undefined
|
|
? html`
|
|
<hr />
|
|
${this._markdownLoading ? this._renderLoader() : nothing}
|
|
`
|
|
: html`
|
|
<hr />
|
|
<ha-markdown
|
|
@content-resize=${this._markdownLoaded}
|
|
.content=${this._releaseNotes}
|
|
class=${this._markdownLoading ? "hidden" : ""}
|
|
></ha-markdown>
|
|
${this._markdownLoading ? this._renderLoader() : nothing}
|
|
`
|
|
: this.stateObj.attributes.release_summary
|
|
? html`
|
|
<hr />
|
|
<ha-markdown
|
|
@content-resize=${this._markdownLoaded}
|
|
.content=${this.stateObj.attributes.release_summary}
|
|
class=${this._markdownLoading ? "hidden" : ""}
|
|
></ha-markdown>
|
|
${this._markdownLoading ? this._renderLoader() : nothing}
|
|
`
|
|
: nothing}
|
|
</div>
|
|
<div class="footer">
|
|
${createBackupTexts
|
|
? html`
|
|
<ha-md-list>
|
|
<ha-md-list-item>
|
|
<span slot="headline">${createBackupTexts.title}</span>
|
|
${createBackupTexts.description
|
|
? html`
|
|
<span slot="supporting-text">
|
|
${createBackupTexts.description}
|
|
</span>
|
|
`
|
|
: nothing}
|
|
<ha-switch
|
|
slot="end"
|
|
id="create-backup"
|
|
.disabled=${updateIsInstalling(this.stateObj)}
|
|
></ha-switch>
|
|
</ha-md-list-item>
|
|
</ha-md-list>
|
|
`
|
|
: nothing}
|
|
<div class="actions">
|
|
${this.stateObj.state === BINARY_STATE_OFF &&
|
|
this.stateObj.attributes.skipped_version
|
|
? html`
|
|
<ha-button @click=${this._handleClearSkipped}>
|
|
${this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.clear_skipped"
|
|
)}
|
|
</ha-button>
|
|
`
|
|
: html`
|
|
<ha-button
|
|
@click=${this._handleSkip}
|
|
.disabled=${skippedVersion ||
|
|
this.stateObj.state === BINARY_STATE_OFF ||
|
|
updateIsInstalling(this.stateObj)}
|
|
>
|
|
${this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.skip"
|
|
)}
|
|
</ha-button>
|
|
`}
|
|
${supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL)
|
|
? html`
|
|
<ha-button
|
|
@click=${this._handleInstall}
|
|
.disabled=${(this.stateObj.state === BINARY_STATE_OFF &&
|
|
!skippedVersion) ||
|
|
updateIsInstalling(this.stateObj)}
|
|
>
|
|
${this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.update"
|
|
)}
|
|
</ha-button>
|
|
`
|
|
: nothing}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private _renderLoader() {
|
|
return html`
|
|
<div class="flex center loader">
|
|
<ha-circular-progress indeterminate></ha-circular-progress>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
protected firstUpdated(): void {
|
|
if (supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES)) {
|
|
this._fetchReleaseNotes();
|
|
}
|
|
if (supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
|
|
this._fetchEntitySources().then(() => {
|
|
const type = getUpdateType(this.stateObj!, this._entitySources!);
|
|
if (type === "home_assistant") {
|
|
this._fetchBackupConfig();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private async _markdownLoaded() {
|
|
if (this._markdownLoading) {
|
|
this._markdownLoading = false;
|
|
}
|
|
}
|
|
|
|
private async _fetchReleaseNotes() {
|
|
try {
|
|
this._releaseNotes = await updateReleaseNotes(
|
|
this.hass,
|
|
this.stateObj!.entity_id
|
|
);
|
|
} catch (err: any) {
|
|
this._error = err.message;
|
|
}
|
|
}
|
|
|
|
get _shouldCreateBackup(): boolean {
|
|
if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
|
|
return false;
|
|
}
|
|
const createBackupSwitch = this.shadowRoot?.getElementById(
|
|
"create-backup"
|
|
) as HaSwitch;
|
|
if (createBackupSwitch) {
|
|
return createBackupSwitch.checked;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private _handleInstall(): void {
|
|
const installData: Record<string, any> = {
|
|
entity_id: this.stateObj!.entity_id,
|
|
};
|
|
|
|
if (this._shouldCreateBackup) {
|
|
installData.backup = true;
|
|
}
|
|
|
|
if (
|
|
supportsFeature(this.stateObj!, UpdateEntityFeature.SPECIFIC_VERSION) &&
|
|
this.stateObj!.attributes.latest_version
|
|
) {
|
|
installData.version = this.stateObj!.attributes.latest_version;
|
|
}
|
|
|
|
this.hass.callService("update", "install", installData);
|
|
}
|
|
|
|
private _handleSkip(): void {
|
|
if (this.stateObj!.attributes.auto_update) {
|
|
showAlertDialog(this, {
|
|
title: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.auto_update_enabled_title"
|
|
),
|
|
text: this.hass.localize(
|
|
"ui.dialogs.more_info_control.update.auto_update_enabled_text"
|
|
),
|
|
});
|
|
return;
|
|
}
|
|
this.hass.callService("update", "skip", {
|
|
entity_id: this.stateObj!.entity_id,
|
|
});
|
|
}
|
|
|
|
private _handleClearSkipped(): void {
|
|
this.hass.callService("update", "clear_skipped", {
|
|
entity_id: this.stateObj!.entity_id,
|
|
});
|
|
}
|
|
|
|
static styles = css`
|
|
:host {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
justify-content: space-between;
|
|
}
|
|
hr {
|
|
border-color: var(--divider-color);
|
|
border-bottom: none;
|
|
margin: 16px 0;
|
|
}
|
|
ha-expansion-panel {
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.summary {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.row {
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.footer {
|
|
border-top: 1px solid var(--divider-color);
|
|
background: var(
|
|
--ha-dialog-surface-background,
|
|
var(--mdc-theme-surface, #fff)
|
|
);
|
|
position: sticky;
|
|
bottom: 0;
|
|
margin: 0 -24px 0 -24px;
|
|
margin-bottom: calc(-1 * max(env(safe-area-inset-bottom), 24px));
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
z-index: 10;
|
|
}
|
|
|
|
ha-md-list {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
margin-bottom: -16px;
|
|
margin-top: -4px;
|
|
}
|
|
|
|
ha-md-list-item {
|
|
--md-list-item-leading-space: 24px;
|
|
--md-list-item-trailing-space: 24px;
|
|
}
|
|
|
|
.actions {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
box-sizing: border-box;
|
|
padding: 12px;
|
|
z-index: 1;
|
|
gap: 8px;
|
|
}
|
|
|
|
a {
|
|
color: var(--primary-color);
|
|
}
|
|
.flex.center {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
mwc-linear-progress {
|
|
margin-bottom: -8px;
|
|
margin-top: 4px;
|
|
}
|
|
ha-markdown {
|
|
direction: ltr;
|
|
padding-bottom: 16px;
|
|
box-sizing: border-box;
|
|
}
|
|
ha-markdown.hidden {
|
|
display: none;
|
|
}
|
|
.loader {
|
|
height: 80px;
|
|
box-sizing: border-box;
|
|
padding-bottom: 16px;
|
|
}
|
|
`;
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"more-info-update": MoreInfoUpdate;
|
|
}
|
|
}
|