From bbca7b762bf92570044a23bf8a0e8dbc59b57004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 6 Apr 2022 22:21:06 +0200 Subject: [PATCH] Use selectors for add-on network configuration (#12235) * Use selectors for add-on network configuration * Show container port as UOM if advanced user * adjust --- .../addon-view/config/hassio-addon-network.ts | 172 ++++++++++-------- src/data/hassio/addon.ts | 5 +- src/translations/en.json | 3 +- 3 files changed, 98 insertions(+), 82 deletions(-) diff --git a/hassio/src/addon-view/config/hassio-addon-network.ts b/hassio/src/addon-view/config/hassio-addon-network.ts index ae3deed1db..66c4853551 100644 --- a/hassio/src/addon-view/config/hassio-addon-network.ts +++ b/hassio/src/addon-view/config/hassio-addon-network.ts @@ -1,4 +1,3 @@ -import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, @@ -8,10 +7,13 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-form/ha-form"; +import type { HaFormSchema } from "../../../../src/components/ha-form/types"; import { HassioAddonDetails, HassioAddonSetOptionParams, @@ -24,16 +26,6 @@ import { HomeAssistant } from "../../../../src/types"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { hassioStyle } from "../../resources/hassio-style"; -interface NetworkItem { - description: string; - container: string; - host: number | null; -} - -interface NetworkItemInput extends PaperInputElement { - container: string; -} - @customElement("hassio-addon-network") class HassioAddonNetwork extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -42,9 +34,13 @@ class HassioAddonNetwork extends LitElement { @property({ attribute: false }) public addon!: HassioAddonDetails; + @state() private _showOptional = false; + + @state() private _configHasChanged = false; + @state() private _error?: string; - @state() private _config?: NetworkItem[]; + @state() private _config?: Record; public connectedCallback(): void { super.connectedCallback(); @@ -56,6 +52,10 @@ class HassioAddonNetwork extends LitElement { return html``; } + const hasHiddenOptions = Object.keys(this._config).find( + (entry) => this._config![entry] === null + ); + return html`
+

+ ${this.supervisor.localize( + "addon.configuration.network.introduction" + )} +

${this._error ? html`${this._error}` : ""} - - - - - - - - ${this._config!.map( - (item) => html` - - - - - - ` - )} - -
- ${this.supervisor.localize( - "addon.configuration.network.container" - )} - - ${this.supervisor.localize( - "addon.configuration.network.host" - )} - ${this.supervisor.localize("common.description")}
${item.container} - - ${this._computeDescription(item)}
+
+ ${hasHiddenOptions + ? html` + + + ` + : ""}
${this.supervisor.localize("common.reset_defaults")} - + ${this.supervisor.localize("common.save")}
@@ -123,50 +120,60 @@ class HassioAddonNetwork extends LitElement { } } - private _computeDescription = (item: NetworkItem): string => - this.addon.translations[this.hass.language]?.network?.[item.container] - ?.description || - this.addon.translations.en?.network?.[item.container]?.description || - item.description; + private _createSchema = memoizeOne( + ( + config: Record, + showOptional: boolean, + advanced: boolean + ): HaFormSchema[] => + (showOptional + ? Object.keys(config) + : Object.keys(config).filter((entry) => config[entry] !== null) + ).map((entry) => ({ + name: entry, + selector: { + number: { + mode: "box", + min: 0, + max: 65535, + unit_of_measurement: advanced ? entry : undefined, + }, + }, + })) + ); + + private _computeLabel = (_: HaFormSchema): string => ""; + + private _computeHelper = (item: HaFormSchema): string => + this.addon.translations[this.hass.language]?.network?.[item.name] || + this.addon.translations.en?.network?.[item.name] || + this.addon.network_description?.[item.name] || + item.name; private _setNetworkConfig(): void { - const network = this.addon.network || {}; - const description = this.addon.network_description || {}; - const items: NetworkItem[] = Object.keys(network).map((key) => ({ - container: key, - host: network[key], - description: description[key], - })); - this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1)); + this._config = this.addon.network || {}; } - private async _configChanged(ev: Event): Promise { - const target = ev.target as NetworkItemInput; - this._config!.forEach((item) => { - if ( - item.container === target.container && - item.host !== parseInt(String(target.value), 10) - ) { - item.host = target.value ? parseInt(String(target.value), 10) : null; - } - }); + private async _configChanged(ev: CustomEvent): Promise { + this._configHasChanged = true; + this._config! = ev.detail.value; } private async _resetTapped(ev: CustomEvent): Promise { const button = ev.currentTarget as any; - button.progress = true; - const data: HassioAddonSetOptionParams = { network: null, }; try { await setHassioAddonOption(this.hass, this.addon.slug, data); + this._configHasChanged = false; const eventdata = { success: true, response: undefined, path: "option", }; + button.actionSuccess(); fireEvent(this, "hass-api-called", eventdata); if (this.addon?.state === "started") { await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); @@ -177,19 +184,21 @@ class HassioAddonNetwork extends LitElement { "error", extractApiErrorMessage(err) ); + button.actionError(); } + } - button.progress = false; + private _toggleOptional() { + this._showOptional = !this._showOptional; } private async _saveTapped(ev: CustomEvent): Promise { const button = ev.currentTarget as any; - button.progress = true; this._error = undefined; const networkconfiguration = {}; - this._config!.forEach((item) => { - networkconfiguration[item.container] = parseInt(String(item.host), 10); + Object.entries(this._config!).forEach(([key, value]) => { + networkconfiguration[key] = value ?? null; }); const data: HassioAddonSetOptionParams = { @@ -198,11 +207,13 @@ class HassioAddonNetwork extends LitElement { try { await setHassioAddonOption(this.hass, this.addon.slug, data); + this._configHasChanged = false; const eventdata = { success: true, response: undefined, path: "option", }; + button.actionSuccess(); fireEvent(this, "hass-api-called", eventdata); if (this.addon?.state === "started") { await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); @@ -213,8 +224,8 @@ class HassioAddonNetwork extends LitElement { "error", extractApiErrorMessage(err) ); + button.actionError(); } - button.progress = false; } static get styles(): CSSResultGroup { @@ -232,6 +243,9 @@ class HassioAddonNetwork extends LitElement { display: flex; justify-content: space-between; } + .show-optional { + padding: 16px; + } `, ]; } diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 0cead4ee9a..c27d735b0e 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -21,7 +21,8 @@ export type AddonState = "started" | "stopped" | null; export type AddonRepository = "core" | "local" | string; interface AddonTranslations { - [key: string]: Record>>; + network?: Record; + configuration?: Record; } export interface HassioAddonInfo { @@ -91,7 +92,7 @@ export interface HassioAddonDetails extends HassioAddonInfo { slug: string; startup: AddonStartup; stdin: boolean; - translations: AddonTranslations; + translations: Record; watchdog: null | boolean; webui: null | string; } diff --git a/src/translations/en.json b/src/translations/en.json index e4cde9f090..eebf0e0304 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4171,7 +4171,8 @@ "container": "Container", "disabled": "Disabled", "header": "Network", - "host": "Host" + "show_disabled": "Show disabled ports", + "introduction": "Change the ports on your host that are exposed by the add-on" } }, "dashboard": {