mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-31 06:29:43 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			20220902.0
			...
			update-int
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 3703ffc42d | ||
|   | 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 | ||||||
|   | |||||||
| @@ -1,58 +0,0 @@ | |||||||
| import { HomeAssistant } from "../../types"; |  | ||||||
|  |  | ||||||
| interface SupervisorBaseAvailableUpdates { |  | ||||||
|   panel_path?: string; |  | ||||||
|   update_type?: string; |  | ||||||
|   version_latest?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface SupervisorAddonAvailableUpdates |  | ||||||
|   extends SupervisorBaseAvailableUpdates { |  | ||||||
|   update_type?: "addon"; |  | ||||||
|   icon?: string; |  | ||||||
|   name?: string; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface SupervisorCoreAvailableUpdates |  | ||||||
|   extends SupervisorBaseAvailableUpdates { |  | ||||||
|   update_type?: "core"; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface SupervisorOsAvailableUpdates extends SupervisorBaseAvailableUpdates { |  | ||||||
|   update_type?: "os"; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface SupervisorSupervisorAvailableUpdates |  | ||||||
|   extends SupervisorBaseAvailableUpdates { |  | ||||||
|   update_type?: "supervisor"; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export type SupervisorAvailableUpdates = |  | ||||||
|   | SupervisorAddonAvailableUpdates |  | ||||||
|   | SupervisorCoreAvailableUpdates |  | ||||||
|   | SupervisorOsAvailableUpdates |  | ||||||
|   | SupervisorSupervisorAvailableUpdates; |  | ||||||
|  |  | ||||||
| export interface SupervisorAvailableUpdatesResponse { |  | ||||||
|   available_updates: SupervisorAvailableUpdates[]; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const fetchSupervisorAvailableUpdates = async ( |  | ||||||
|   hass: HomeAssistant |  | ||||||
| ): Promise<SupervisorAvailableUpdates[]> => |  | ||||||
|   ( |  | ||||||
|     await hass.callWS<SupervisorAvailableUpdatesResponse>({ |  | ||||||
|       type: "supervisor/api", |  | ||||||
|       endpoint: "/available_updates", |  | ||||||
|       method: "get", |  | ||||||
|     }) |  | ||||||
|   ).available_updates; |  | ||||||
|  |  | ||||||
| export const refreshSupervisorAvailableUpdates = async ( |  | ||||||
|   hass: HomeAssistant |  | ||||||
| ): Promise<void> => |  | ||||||
|   hass.callWS<void>({ |  | ||||||
|     type: "supervisor/api", |  | ||||||
|     endpoint: "/refresh_updates", |  | ||||||
|     method: "post", |  | ||||||
|   }); |  | ||||||
							
								
								
									
										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,20 +155,23 @@ 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" | ||||||
|  |                 )} | ||||||
|  |               </ha-alert>` | ||||||
|  |             : this.updates?.length | ||||||
|             ? html`<ha-card> |             ? html`<ha-card> | ||||||
|                 <ha-config-updates |                 <ha-config-updates | ||||||
|                   .hass=${this.hass} |                   .hass=${this.hass} | ||||||
|                   .narrow=${this.narrow} |                   .narrow=${this.narrow} | ||||||
|                         .supervisorUpdates=${this.supervisorUpdates} |                   .updates=${this.updates} | ||||||
|                 ></ha-config-updates> |                 ></ha-config-updates> | ||||||
|               </ha-card>` |               </ha-card>` | ||||||
|             : ""} |             : ""} | ||||||
|           <ha-card> |           <ha-card> | ||||||
|                   ${this.narrow && this.supervisorUpdates?.length |             ${this.narrow && this.updates?.length | ||||||
|               ? html`<div class="title"> |               ? html`<div class="title"> | ||||||
|                   ${this.hass.localize("panel.config")} |                   ${this.hass.localize("panel.config")} | ||||||
|                 </div>` |                 </div>` | ||||||
| @@ -203,7 +201,7 @@ class HaConfigDashboard extends LitElement { | |||||||
|               .showAdvanced=${this.showAdvanced} |               .showAdvanced=${this.showAdvanced} | ||||||
|               .pages=${configSections.dashboard} |               .pages=${configSections.dashboard} | ||||||
|             ></ha-config-navigation> |             ></ha-config-navigation> | ||||||
|                 </ha-card>`} |           </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"> | ||||||
|                 ${update.update_type === "addon" |               <img | ||||||
|                   ? update.icon |                 src=${update.icon_url || | ||||||
|                     ? html`<img src="/api/hassio${update.icon}" />` |                 brandsUrl({ | ||||||
|                     : html`<ha-svg-icon |                   domain: update.domain, | ||||||
|                         .path=${mdiPackageVariant} |                   type: "icon", | ||||||
|                       ></ha-svg-icon>` |                   useFallback: true, | ||||||
|                   : html`<ha-logo-svg></ha-logo-svg>`} |                   darkOptimized: this.hass.themes?.darkMode, | ||||||
|  |                 })} | ||||||
|  |               /> | ||||||
|             </span> |             </span> | ||||||
|             <paper-item-body two-line> |             <paper-item-body two-line> | ||||||
|                 ${update.update_type === "addon" |               ${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.version_latest, |                     version_available: update.available_version, | ||||||
|                   } |                   } | ||||||
|                 )} |                 )} | ||||||
|               </div> |               </div> | ||||||
|             </paper-item-body> |             </paper-item-body> | ||||||
|               ${!this.narrow ? html`<ha-icon-next></ha-icon-next>` : ""} |  | ||||||
|           </paper-icon-item> |           </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", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user