diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 986090e53f..5800d2e1b0 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -114,11 +114,22 @@ class HassioAddonInfo extends LitElement { @state() private _error?: string; + private _fetchDataTimeout?: number; + private _addonStoreInfo = memoizeOne( (slug: string, storeAddons: StoreAddon[]) => storeAddons.find((addon) => addon.slug === slug) ); + public disconnectedCallback() { + super.disconnectedCallback(); + + if (this._fetchDataTimeout) { + clearInterval(this._fetchDataTimeout); + this._fetchDataTimeout = undefined; + } + } + protected render(): TemplateResult { const addonStoreInfo = !this.addon.detached && !this.addon.available @@ -592,7 +603,10 @@ class HassioAddonInfo extends LitElement { ` : html` - + ${this.supervisor.localize("addon.dashboard.start")} ` @@ -672,9 +686,36 @@ class HassioAddonInfo extends LitElement { super.updated(changedProps); if (changedProps.has("addon")) { this._loadData(); + if ( + !this._fetchDataTimeout && + this.addon && + "state" in this.addon && + this.addon.state === "startup" + ) { + // Addon is starting up, wait for it to start + this._scheduleDataUpdate(); + } } } + private _scheduleDataUpdate() { + this._fetchDataTimeout = window.setTimeout(async () => { + const addon = await fetchHassioAddonInfo(this.hass, this.addon.slug); + if (addon.state !== "startup") { + this._fetchDataTimeout = undefined; + this.addon = addon; + const eventdata = { + success: true, + response: undefined, + path: "start", + }; + fireEvent(this, "hass-api-called", eventdata); + } else { + this._scheduleDataUpdate(); + } + }, 500); + } + private async _loadData(): Promise { if ("state" in this.addon && this.addon.state === "started") { this._metrics = await fetchHassioStats( diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts index f2ac689f87..5f50d0600c 100644 --- a/hassio/src/ingress-view/hassio-ingress-view.ts +++ b/hassio/src/ingress-view/hassio-ingress-view.ts @@ -16,6 +16,7 @@ import "../../../src/components/ha-icon-button"; import { fetchHassioAddonInfo, HassioAddonDetails, + startHassioAddon, } from "../../../src/data/hassio/addon"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { @@ -23,7 +24,10 @@ import { validateHassioSession, } from "../../../src/data/hassio/ingress"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; -import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../src/dialogs/generic/show-dialog-box"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-subpage"; import { HomeAssistant, Route } from "../../../src/types"; @@ -45,6 +49,8 @@ class HassioIngressView extends LitElement { private _sessionKeepAlive?: number; + private _fetchDataTimeout?: number; + public disconnectedCallback() { super.disconnectedCallback(); @@ -52,16 +58,21 @@ class HassioIngressView extends LitElement { clearInterval(this._sessionKeepAlive); this._sessionKeepAlive = undefined; } + if (this._fetchDataTimeout) { + clearInterval(this._fetchDataTimeout); + this._fetchDataTimeout = undefined; + } } protected render(): TemplateResult { if (!this._addon) { - return html` `; + return html``; } const iframe = html``; @@ -132,10 +143,10 @@ class HassioIngressView extends LitElement { return; } - const addon = this.route.path.substr(1); + const addon = this.route.path.substring(1); const oldRoute = changedProps.get("route") as this["route"] | undefined; - const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined; + const oldAddon = oldRoute ? oldRoute.path.substring(1) : undefined; if (addon && addon !== oldAddon) { this._fetchData(addon); @@ -145,33 +156,23 @@ class HassioIngressView extends LitElement { private async _fetchData(addonSlug: string) { const createSessionPromise = createHassioSession(this.hass); - let addon; + let addon: HassioAddonDetails; try { addon = await fetchHassioAddonInfo(this.hass, addonSlug); } catch (err: any) { await showAlertDialog(this, { - text: "Unable to fetch add-on info to start Ingress", + text: this.supervisor.localize("ingress.error_addon_info"), title: "Supervisor", }); await nextRender(); - history.back(); + navigate("/hassio/store", { replace: true }); return; } - if (!addon.ingress_url) { + if (!addon.version) { await showAlertDialog(this, { - text: "Add-on does not support Ingress", - title: addon.name, - }); - await nextRender(); - history.back(); - return; - } - - if (addon.state !== "started") { - await showAlertDialog(this, { - text: "Add-on is not running. Please start it first", + text: this.supervisor.localize("ingress.error_addon_not_installed"), title: addon.name, }); await nextRender(); @@ -179,13 +180,74 @@ class HassioIngressView extends LitElement { return; } - let session; + if (!addon.ingress_url) { + await showAlertDialog(this, { + text: this.supervisor.localize("ingress.error_addon_not_supported"), + title: addon.name, + }); + await nextRender(); + history.back(); + return; + } + + if (!addon.state || !["startup", "started"].includes(addon.state)) { + const confirm = await showConfirmationDialog(this, { + text: this.supervisor.localize("ingress.error_addon_not_running"), + title: addon.name, + confirmText: this.supervisor.localize("ingress.start_addon"), + dismissText: this.supervisor.localize("common.no"), + }); + if (confirm) { + try { + await startHassioAddon(this.hass, addonSlug); + fireEvent(this, "supervisor-collection-refresh", { + collection: "addon", + }); + this._fetchData(addonSlug); + return; + } catch (e) { + await showAlertDialog(this, { + text: this.supervisor.localize("ingress.error_starting_addon"), + title: addon.name, + }); + await nextRender(); + navigate(`/hassio/addon/${addon.slug}/logs`, { replace: true }); + return; + } + } else { + await nextRender(); + navigate(`/hassio/addon/${addon.slug}/info`, { replace: true }); + return; + } + } + + if (addon.state === "startup") { + // Addon is starting up, wait for it to start + this._fetchDataTimeout = window.setTimeout(() => { + this._fetchData(addonSlug); + }, 500); + return; + } + + if (addon.state !== "started") { + return; + } + + if (this._fetchDataTimeout) { + clearInterval(this._fetchDataTimeout); + this._fetchDataTimeout = undefined; + } + + let session: string; try { session = await createSessionPromise; } catch (err: any) { + if (this._sessionKeepAlive) { + clearInterval(this._sessionKeepAlive); + } await showAlertDialog(this, { - text: "Unable to create an Ingress session", + text: this.supervisor.localize("ingress.error_creating_session"), title: addon.name, }); await nextRender(); @@ -207,6 +269,31 @@ class HassioIngressView extends LitElement { this._addon = addon; } + private _checkLoaded(ev): void { + if (!this._addon) { + return; + } + if (ev.target.contentDocument.body.textContent === "502: Bad Gateway") { + showConfirmationDialog(this, { + text: this.supervisor.localize("ingress.error_addon_not_ready"), + title: this._addon.name, + confirmText: this.supervisor.localize("ingress.retry"), + dismissText: this.supervisor.localize("common.no"), + confirm: async () => { + const addon = this._addon; + this._addon = undefined; + await Promise.all([ + this.updateComplete, + new Promise((resolve) => { + setTimeout(resolve, 500); + }), + ]); + this._addon = addon; + }, + }); + } + } + private _toggleMenu(): void { fireEvent(this, "hass-toggle-menu"); } diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 9b8aa7c2c6..def1e765e5 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -23,7 +23,13 @@ export type AddonStartup = | "services" | "application" | "once"; -export type AddonState = "started" | "stopped" | null; +export type AddonState = + | "startup" + | "started" + | "stopped" + | "unknown" + | "error" + | null; export type AddonRepository = "core" | "local" | string; interface AddonTranslations { diff --git a/src/translations/en.json b/src/translations/en.json index 865a1d23db..405eb44332 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5834,10 +5834,20 @@ "error": "[%key:ui::panel::my::error%]", "error_addon_not_found": "Add-on not found", "error_repository_not_found": "The required repository for this Add-on was not found", - "error_addon_not_started": "The requested add-on is not running. Please start it first", "error_addon_not_installed": "The requested add-on is not installed. Please install it first", "error_addon_no_ingress": "The requested add-on does not support ingress" }, + "ingress": { + "error_addon_info": "Unable to fetch add-on info to start Ingress", + "error_addon_not_installed": "The add-on is not installed. Please install it first", + "error_addon_not_supported": "This add-on does not support Ingress", + "error_addon_not_running": "Add-on is not running. Do you want to start it now?", + "start_addon": "Start add-on", + "error_starting_addon": "Error starting the add-on", + "error_creating_session": "Unable to create an Ingress session", + "error_addon_not_ready": "The add-on seems to not be ready, it might still be starting. Do you want to try again?", + "retry": "Retry" + }, "system": { "log": { "log_provider": "Log Provider",