From af0f1c8f4775880d164615370902b7a25fb1a196 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 22 Apr 2025 11:37:48 +0200 Subject: [PATCH] Add system managed addon info dialog --- .../src/addon-view/hassio-addon-dashboard.ts | 33 ++- .../src/addon-view/info/hassio-addon-info.ts | 38 ++- .../markdown/dialog-hassio-markdown.ts | 1 + .../system-managed/dialog-system-managed.ts | 242 ++++++++++++++++++ .../show-dialog-system-managed.ts | 22 ++ src/data/hassio/addon.ts | 2 + src/translations/en.json | 7 + 7 files changed, 338 insertions(+), 7 deletions(-) create mode 100644 hassio/src/dialogs/system-managed/dialog-system-managed.ts create mode 100644 hassio/src/dialogs/system-managed/show-dialog-system-managed.ts diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts index dc5cdb5d66..513bc2b332 100644 --- a/hassio/src/addon-view/hassio-addon-dashboard.ts +++ b/hassio/src/addon-view/hassio-addon-dashboard.ts @@ -8,6 +8,7 @@ import type { CSSResultGroup, TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { storage } from "../../../src/common/decorators/storage"; import { fireEvent } from "../../../src/common/dom/fire_event"; import { navigate } from "../../../src/common/navigate"; import { extractSearchParam } from "../../../src/common/url/search-params"; @@ -31,6 +32,7 @@ import "../../../src/layouts/hass-tabs-subpage"; import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage"; import { haStyle } from "../../../src/resources/styles"; import type { HomeAssistant, Route } from "../../../src/types"; +import { showSystemManagedDialog } from "../dialogs/system-managed/show-dialog-system-managed"; import { hassioStyle } from "../resources/hassio-style"; import "./config/hassio-addon-audio"; import "./config/hassio-addon-config"; @@ -52,6 +54,14 @@ class HassioAddonDashboard extends LitElement { @property({ type: Boolean }) public narrow = false; + @storage({ + storage: "sessionStorage", + key: `hassio-addon-system-managed-info-dismissed`, + state: true, + subscribe: false, + }) + private _dismissedAddons: string[] = []; + @state() private _error?: string; private _backPath = new URLSearchParams(window.parent.location.search).get( @@ -270,7 +280,28 @@ class HassioAddonDashboard extends LitElement { const addonsInfo = await fetchHassioAddonsInfo(this.hass); fireEvent(this, "supervisor-update", { addon: addonsInfo }); } - this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon); + this.addon = (await fetchAddonInfo( + this.hass, + this.supervisor, + addon + )) as HassioAddonDetails; + + if ( + this.addon.system_managed && + !this._dismissedAddons.includes(this.addon.slug) + ) { + showSystemManagedDialog(this, { + addon: this.addon, + backPath: this._backPath, + supervisor: this.supervisor, + dismiss: () => { + this._dismissedAddons = [ + ...this._dismissedAddons, + this.addon!.slug, + ]; + }, + }); + } } catch (err: any) { this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; this.addon = undefined; diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 02e1f627df..cd235420bf 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -2,7 +2,6 @@ import "@material/mwc-button"; import { mdiCheckCircle, mdiChip, - mdiPlayCircle, mdiCircleOffOutline, mdiCursorDefaultClickOutline, mdiDocker, @@ -19,27 +18,29 @@ import { mdiNumeric6, mdiNumeric7, mdiNumeric8, + mdiPlayCircle, mdiPound, mdiShield, } from "@mdi/js"; import type { CSSResultGroup, TemplateResult } from "lit"; -import { LitElement, css, html } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { atLeastVersion } from "../../../../src/common/config/version"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import { navigate } from "../../../../src/common/navigate"; +import { capitalizeFirstLetter } from "../../../../src/common/string/capitalize-first-letter"; import "../../../../src/components/buttons/ha-progress-button"; +import "../../../../src/components/chips/ha-assist-chip"; +import "../../../../src/components/chips/ha-chip-set"; import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-card"; -import "../../../../src/components/chips/ha-chip-set"; -import "../../../../src/components/chips/ha-assist-chip"; +import "../../../../src/components/ha-formfield"; import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-switch"; -import "../../../../src/components/ha-formfield"; import type { HaSwitch } from "../../../../src/components/ha-switch"; import type { AddonCapability, @@ -81,10 +82,10 @@ import { bytesToString } from "../../../../src/util/bytes-to-string"; import "../../components/hassio-card-content"; import "../../components/supervisor-metric"; import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown"; +import { showSystemManagedDialog } from "../../dialogs/system-managed/show-dialog-system-managed"; import { hassioStyle } from "../../resources/hassio-style"; import "../../update-available/update-available-card"; import { addonArchIsSupported, extractChangelog } from "../../util/addon"; -import { capitalizeFirstLetter } from "../../../../src/common/string/capitalize-first-letter"; const STAGE_ICON = { stable: mdiCheckCircle, @@ -456,6 +457,23 @@ class HassioAddonInfo extends LitElement { ` : ""} + ${"system_managed" in this.addon && this.addon.system_managed + ? html` + + + + ` + : nothing}
@@ -822,6 +840,14 @@ class HassioAddonInfo extends LitElement { }); } + private _showSystemManagedDialog() { + showSystemManagedDialog(this, { + addon: this.addon as HassioAddonDetails, + closeable: true, + supervisor: this.supervisor, + }); + } + private get _computeIsRunning(): boolean { return (this.addon as HassioAddonDetails)?.state === "started"; } diff --git a/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts b/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts index d7fcc8f889..740ee1dcfc 100644 --- a/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts +++ b/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts @@ -38,6 +38,7 @@ class HassioMarkdownDialog extends LitElement { open @closed=${this.closeDialog} .heading=${createCloseHeading(this.hass, this.title)} + hideactions > void; + + @state() private _closeable = false; + + @state() private _open = false; + + @state() private _configEntry?: ConfigEntry; + + @query("ha-md-dialog") private _dialog?: HaMdDialog; + + public async showDialog( + dialogParams: SystemManagedDialogParams + ): Promise { + this._addon = dialogParams.addon; + this._backPath = dialogParams.backPath; + this._supervisor = dialogParams.supervisor; + this._dismiss = dialogParams.dismiss; + this._closeable = dialogParams.closeable ?? false; + this._open = true; + this._loadConfigEntry(); + } + + private _dialogClosed() { + this._addon = undefined; + this._backPath = undefined; + this._supervisor = undefined; + this._dismiss = undefined; + this._closeable = false; + this._configEntry = undefined; + this._open = false; + } + + public closeDialog() { + this._dialog?.close(); + return true; + } + + protected render() { + if (!this._addon || !this._open || !this._supervisor) { + return nothing; + } + + const addonImage = + atLeastVersion(this.hass.config.version, 0, 105) && this._addon.icon + ? `/api/hassio/addons/${this._addon.slug}/icon` + : undefined; + + return html` + + + ${this._closeable + ? html` + + ` + : nothing} + ${this._addon?.name} + +
+
+ + + ${addonImage + ? html`${this._addon.name}` + : html``} +
+ ${this._supervisor.localize("addon.system_managed.description")} + ${this._configEntry + ? html` +

+ ${this._supervisor.localize( + "addon.system_managed.managed_by" + )}: +

+ + + ${this._configEntry.title} + ${this._configEntry.title} + + + + ` + : nothing} +
+ ${this._dismiss || this._backPath + ? html`
+ ${this._backPath + ? html` + ${this._supervisor.localize("common.back")} + ` + : nothing} + ${this._dismiss + ? html` + ${this._supervisor.localize( + "addon.system_managed.show_addon" + )} + ` + : nothing} +
` + : nothing} +
+ `; + } + + private _onImageLoad(ev) { + ev.target.style.visibility = "initial"; + } + + private _onImageError(ev) { + ev.target.style.visibility = "hidden"; + } + + private async _loadConfigEntry() { + if (this._addon?.system_managed_config_entry) { + try { + const { config_entry } = await getConfigEntry( + this.hass, + this._addon.system_managed_config_entry + ); + this._configEntry = config_entry; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + } + } + + private _navigateBack() { + if (!this._backPath) { + return; + } + navigate(this._backPath); + this._dialogClosed(); + } + + private _showAddon() { + this.closeDialog(); + this._dismiss?.(); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + .icons { + display: flex; + justify-content: center; + align-items: center; + gap: 16px; + --mdc-icon-size: 48px; + margin-bottom: 32px; + } + .icons img { + width: 48px; + } + .icons .primary { + color: var(--primary-color); + } + .actions { + display: flex; + justify-content: space-between; + } + .integration-icon { + width: 24px; + } + ha-md-list-item { + --md-list-item-leading-space: 4px; + --md-list-item-trailing-space: 4px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-system-managed": HassioSystemManagedDialog; + } +} diff --git a/hassio/src/dialogs/system-managed/show-dialog-system-managed.ts b/hassio/src/dialogs/system-managed/show-dialog-system-managed.ts new file mode 100644 index 0000000000..de21b63927 --- /dev/null +++ b/hassio/src/dialogs/system-managed/show-dialog-system-managed.ts @@ -0,0 +1,22 @@ +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import type { HassioAddonDetails } from "../../../../src/data/hassio/addon"; +import type { Supervisor } from "../../../../src/data/supervisor/supervisor"; + +export interface SystemManagedDialogParams { + addon: HassioAddonDetails; + backPath?: string; + closeable?: boolean; + supervisor: Supervisor; + dismiss?: () => void; +} + +export const showSystemManagedDialog = ( + element: HTMLElement, + dialogParams: SystemManagedDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-system-managed", + dialogImport: () => import("./dialog-system-managed"), + dialogParams, + }); +}; diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 5154f45006..064eaa5613 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -101,6 +101,8 @@ export interface HassioAddonDetails extends HassioAddonInfo { slug: string; startup: AddonStartup; stdin: boolean; + system_managed: boolean; + system_managed_config_entry: string | null; translations: Record; watchdog: null | boolean; webui: null | string; diff --git a/src/translations/en.json b/src/translations/en.json index 6e9e10bff6..3dfc38193c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -8838,6 +8838,12 @@ }, "logs": { "get_logs": "Failed to get add-on logs, {error}" + }, + "system_managed": { + "title": "System managed", + "description": "This addon is managed by Home Assistant core. You should not change settings or options for this addon. If you do, Home Assistant may not be able to manage the addon correctly.", + "show_addon": "Show add-on", + "managed_by": "Managed by" } }, "common": { @@ -8861,6 +8867,7 @@ "running_version": "You are currently running version {version}", "save": "[%key:ui::common::save%]", "close": "[%key:ui::common::close%]", + "back": "[%key:ui::common::back%]", "menu": "[%key:ui::common::menu%]", "show": "[%key:ui::panel::config::updates::show%]", "show_more": "Show more information about this",