Add system managed addon info dialog

This commit is contained in:
Wendelin 2025-04-22 11:37:48 +02:00
parent c73a9fccb8
commit af0f1c8f47
No known key found for this signature in database
7 changed files with 338 additions and 7 deletions

View File

@ -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;

View File

@ -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 {
</ha-assist-chip>
`
: ""}
${"system_managed" in this.addon && this.addon.system_managed
? html`
<ha-assist-chip
filled
@click=${this._showSystemManagedDialog}
id="system_managed"
.label=${capitalizeFirstLetter(
this.supervisor.localize("addon.system_managed.title")
)}
>
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
</ha-assist-chip>
`
: nothing}
</ha-chip-set>
<div class="description light-color">
@ -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";
}

View File

@ -38,6 +38,7 @@ class HassioMarkdownDialog extends LitElement {
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, this.title)}
hideactions
>
<ha-markdown
.content=${this.content || ""}

View File

@ -0,0 +1,242 @@
import { mdiClose, mdiPuzzle, mdiSwapHorizontal } from "@mdi/js";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { atLeastVersion } from "../../../../src/common/config/version";
import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/ha-button";
import "../../../../src/components/ha-dialog-header";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-icon-next";
import "../../../../src/components/ha-md-dialog";
import type { HaMdDialog } from "../../../../src/components/ha-md-dialog";
import "../../../../src/components/ha-md-list";
import "../../../../src/components/ha-md-list-item";
import "../../../../src/components/ha-svg-icon";
import {
getConfigEntry,
type ConfigEntry,
} from "../../../../src/data/config_entries";
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
import { haStyle } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { brandsUrl } from "../../../../src/util/brands-url";
import type { SystemManagedDialogParams } from "./show-dialog-system-managed";
@customElement("dialog-system-managed")
class HassioSystemManagedDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _supervisor?: Supervisor;
@state() private _addon?: HassioAddonDetails;
@state() private _backPath?: string;
@state() private _dismiss?: () => 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<void> {
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`
<ha-md-dialog
open
.disableCancelAction=${!this._closeable}
@closed=${this._dialogClosed}
>
<ha-dialog-header slot="headline">
${this._closeable
? html`
<ha-icon-button
slot="navigationIcon"
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
`
: nothing}
<span slot="title">${this._addon?.name}</span>
</ha-dialog-header>
<div slot="content">
<div class="icons">
<ha-svg-icon
class="primary"
.path=${mdiHomeAssistant}
></ha-svg-icon>
<ha-svg-icon .path=${mdiSwapHorizontal}></ha-svg-icon>
${addonImage
? html`<img src=${addonImage} alt=${this._addon.name} />`
: html`<ha-svg-icon .path=${mdiPuzzle}></ha-svg-icon>`}
</div>
${this._supervisor.localize("addon.system_managed.description")}
${this._configEntry
? html`
<h3>
${this._supervisor.localize(
"addon.system_managed.managed_by"
)}:
</h3>
<ha-md-list>
<ha-md-list-item
type="link"
href=${`/config/integrations/integration/${this._configEntry.domain}`}
>
<img
slot="start"
class="integration-icon"
alt=${this._configEntry.title}
src=${brandsUrl({
domain: this._configEntry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
${this._configEntry.title}
<ha-icon-next slot="end"></ha-icon-next>
</ha-md-list-item>
</ha-md-list>
`
: nothing}
</div>
${this._dismiss || this._backPath
? html` <div slot="actions" class="actions">
${this._backPath
? html`<ha-button @click=${this._navigateBack}>
${this._supervisor.localize("common.back")}
</ha-button>`
: nothing}
${this._dismiss
? html`<ha-button @click=${this._showAddon}>
${this._supervisor.localize(
"addon.system_managed.show_addon"
)}
</ha-button>`
: nothing}
</div>`
: nothing}
</ha-md-dialog>
`;
}
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;
}
}

View File

@ -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,
});
};

View File

@ -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<string, AddonTranslations>;
watchdog: null | boolean;
webui: null | string;

View File

@ -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",