Use selectors for add-on network configuration (#12235)

* Use selectors for add-on network configuration

* Show container port as UOM if advanced user

* adjust
This commit is contained in:
Joakim Sørensen 2022-04-06 22:21:06 +02:00 committed by GitHub
parent 1dba849567
commit bbca7b762b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 82 deletions

View File

@ -1,4 +1,3 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -8,10 +7,13 @@ import {
TemplateResult, TemplateResult,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import { import {
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
@ -24,16 +26,6 @@ import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style"; 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") @customElement("hassio-addon-network")
class HassioAddonNetwork extends LitElement { class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -42,9 +34,13 @@ class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@state() private _showOptional = false;
@state() private _configHasChanged = false;
@state() private _error?: string; @state() private _error?: string;
@state() private _config?: NetworkItem[]; @state() private _config?: Record<string, any>;
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
@ -56,6 +52,10 @@ class HassioAddonNetwork extends LitElement {
return html``; return html``;
} }
const hasHiddenOptions = Object.keys(this._config).find(
(entry) => this._config![entry] === null
);
return html` return html`
<ha-card <ha-card
.header=${this.supervisor.localize( .header=${this.supervisor.localize(
@ -63,52 +63,49 @@ class HassioAddonNetwork extends LitElement {
)} )}
> >
<div class="card-content"> <div class="card-content">
<p>
${this.supervisor.localize(
"addon.configuration.network.introduction"
)}
</p>
${this._error ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>` ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""} : ""}
<table> <ha-form
<tbody> .data=${this._config}
<tr> @value-changed=${this._configChanged}
<th> .computeLabel=${this._computeLabel}
${this.supervisor.localize( .computeHelper=${this._computeHelper}
"addon.configuration.network.container" .schema=${this._createSchema(
)} this._config,
</th> this._showOptional,
<th> this.hass.userData?.showAdvanced || false
${this.supervisor.localize( )}
"addon.configuration.network.host" ></ha-form>
)}
</th>
<th>${this.supervisor.localize("common.description")}</th>
</tr>
${this._config!.map(
(item) => html`
<tr>
<td>${item.container}</td>
<td>
<paper-input
@value-changed=${this._configChanged}
placeholder=${this.supervisor.localize(
"addon.configuration.network.disabled"
)}
.value=${item.host ? String(item.host) : ""}
.container=${item.container}
no-label-float
></paper-input>
</td>
<td>${this._computeDescription(item)}</td>
</tr>
`
)}
</tbody>
</table>
</div> </div>
${hasHiddenOptions
? html`<ha-formfield
class="show-optional"
.label=${this.supervisor.localize(
"addon.configuration.network.show_disabled"
)}
>
<ha-switch
@change=${this._toggleOptional}
.checked=${this._showOptional}
>
</ha-switch>
</ha-formfield>`
: ""}
<div class="card-actions"> <div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}> <ha-progress-button class="warning" @click=${this._resetTapped}>
${this.supervisor.localize("common.reset_defaults")} ${this.supervisor.localize("common.reset_defaults")}
</ha-progress-button> </ha-progress-button>
<ha-progress-button @click=${this._saveTapped}> <ha-progress-button
@click=${this._saveTapped}
.disabled=${!this._configHasChanged}
>
${this.supervisor.localize("common.save")} ${this.supervisor.localize("common.save")}
</ha-progress-button> </ha-progress-button>
</div> </div>
@ -123,50 +120,60 @@ class HassioAddonNetwork extends LitElement {
} }
} }
private _computeDescription = (item: NetworkItem): string => private _createSchema = memoizeOne(
this.addon.translations[this.hass.language]?.network?.[item.container] (
?.description || config: Record<string, number>,
this.addon.translations.en?.network?.[item.container]?.description || showOptional: boolean,
item.description; 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 { private _setNetworkConfig(): void {
const network = this.addon.network || {}; this._config = 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));
} }
private async _configChanged(ev: Event): Promise<void> { private async _configChanged(ev: CustomEvent): Promise<void> {
const target = ev.target as NetworkItemInput; this._configHasChanged = true;
this._config!.forEach((item) => { this._config! = ev.detail.value;
if (
item.container === target.container &&
item.host !== parseInt(String(target.value), 10)
) {
item.host = target.value ? parseInt(String(target.value), 10) : null;
}
});
} }
private async _resetTapped(ev: CustomEvent): Promise<void> { private async _resetTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any; const button = ev.currentTarget as any;
button.progress = true;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
network: null, network: null,
}; };
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = { const eventdata = {
success: true, success: true,
response: undefined, response: undefined,
path: "option", path: "option",
}; };
button.actionSuccess();
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
@ -177,19 +184,21 @@ class HassioAddonNetwork extends LitElement {
"error", "error",
extractApiErrorMessage(err) extractApiErrorMessage(err)
); );
button.actionError();
} }
}
button.progress = false; private _toggleOptional() {
this._showOptional = !this._showOptional;
} }
private async _saveTapped(ev: CustomEvent): Promise<void> { private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any; const button = ev.currentTarget as any;
button.progress = true;
this._error = undefined; this._error = undefined;
const networkconfiguration = {}; const networkconfiguration = {};
this._config!.forEach((item) => { Object.entries(this._config!).forEach(([key, value]) => {
networkconfiguration[item.container] = parseInt(String(item.host), 10); networkconfiguration[key] = value ?? null;
}); });
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
@ -198,11 +207,13 @@ class HassioAddonNetwork extends LitElement {
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = { const eventdata = {
success: true, success: true,
response: undefined, response: undefined,
path: "option", path: "option",
}; };
button.actionSuccess();
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
@ -213,8 +224,8 @@ class HassioAddonNetwork extends LitElement {
"error", "error",
extractApiErrorMessage(err) extractApiErrorMessage(err)
); );
button.actionError();
} }
button.progress = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@ -232,6 +243,9 @@ class HassioAddonNetwork extends LitElement {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.show-optional {
padding: 16px;
}
`, `,
]; ];
} }

View File

@ -21,7 +21,8 @@ export type AddonState = "started" | "stopped" | null;
export type AddonRepository = "core" | "local" | string; export type AddonRepository = "core" | "local" | string;
interface AddonTranslations { interface AddonTranslations {
[key: string]: Record<string, Record<string, Record<string, string>>>; network?: Record<string, string>;
configuration?: Record<string, { name?: string; description?: string }>;
} }
export interface HassioAddonInfo { export interface HassioAddonInfo {
@ -91,7 +92,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
slug: string; slug: string;
startup: AddonStartup; startup: AddonStartup;
stdin: boolean; stdin: boolean;
translations: AddonTranslations; translations: Record<string, AddonTranslations>;
watchdog: null | boolean; watchdog: null | boolean;
webui: null | string; webui: null | string;
} }

View File

@ -4171,7 +4171,8 @@
"container": "Container", "container": "Container",
"disabled": "Disabled", "disabled": "Disabled",
"header": "Network", "header": "Network",
"host": "Host" "show_disabled": "Show disabled ports",
"introduction": "Change the ports on your host that are exposed by the add-on"
} }
}, },
"dashboard": { "dashboard": {