From f277e2e628bf6ed92ef0ae0e3310a80130aa96af Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 22 Oct 2024 10:19:57 +0300 Subject: [PATCH 1/4] Improve Wifi configuration UI --- src/data/hassio/network.ts | 4 +- .../config/network/supervisor-network.ts | 486 ++++++++++-------- src/translations/en.json | 1 + 3 files changed, 276 insertions(+), 215 deletions(-) diff --git a/src/data/hassio/network.ts b/src/data/hassio/network.ts index a0b501d42982..a4d8f7a8541a 100644 --- a/src/data/hassio/network.ts +++ b/src/data/hassio/network.ts @@ -17,7 +17,7 @@ export interface NetworkInterface { ipv4?: Partial; ipv6?: Partial; type: "ethernet" | "wireless" | "vlan"; - wifi?: Partial; + wifi?: Partial | null; } interface DockerNetwork { @@ -27,7 +27,7 @@ interface DockerNetwork { interface: string; } -interface AccessPoint { +export interface AccessPoint { mode: "infrastructure" | "mesh" | "adhoc" | "ap"; ssid: string; mac: string; diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts index 2824d7eadf66..ed6cf36f742c 100644 --- a/src/panels/config/network/supervisor-network.ts +++ b/src/panels/config/network/supervisor-network.ts @@ -20,7 +20,7 @@ import "../../../components/ha-textfield"; import type { HaTextField } from "../../../components/ha-textfield"; import { extractApiErrorMessage } from "../../../data/hassio/common"; import { - AccessPoints, + AccessPoint, accesspointScan, fetchNetworkInfo, formatAddress, @@ -58,7 +58,7 @@ const PREDEFINED_DNS = { export class HassioNetwork extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _accessPoints?: AccessPoints; + @state() private _accessPoints: AccessPoint[] = []; @state() private _curTabIndex = 0; @@ -113,7 +113,7 @@ export class HassioNetwork extends LitElement { ` )} ` - : ""} + : nothing} ${cache(this._renderTab())} `; @@ -122,7 +122,9 @@ export class HassioNetwork extends LitElement { private _renderTab() { return html`
${IP_VERSIONS.map((version) => - this._interface![version] ? this._renderIPConfiguration(version) : "" + this._interface![version] + ? this._renderIPConfiguration(version) + : nothing )} ${this._interface?.type === "wireless" ? html` @@ -131,6 +133,7 @@ export class HassioNetwork extends LitElement { "ui.panel.config.network.supervisor.wifi" )} outlined + .expanded=${!this._interface?.wifi?.ssid} > ${this._interface?.wifi?.ssid ? html`

@@ -139,7 +142,7 @@ export class HassioNetwork extends LitElement { { ssid: this._interface?.wifi?.ssid } )}

` - : ""} + : nothing} - ${this._accessPoints && - this._accessPoints.accesspoints && - this._accessPoints.accesspoints.length !== 0 + ${this._accessPoints.length ? html` - ${this._accessPoints.accesspoints - .filter((ap) => ap.ssid) - .map( - (ap) => html` - - ${ap.ssid} - - ${ap.mac} - - ${this.hass.localize( - "ui.panel.config.network.supervisor.signal_strength" - )}: - ${ap.signal} - - - ` - )} + ${this._accessPoints.map( + (ap) => html` + + ${ap.ssid} + + ${ap.mac} - + ${this.hass.localize( + "ui.panel.config.network.supervisor.signal_strength" + )}: + ${ap.signal} + + + ` + )} ` - : ""} + : nothing} ${this._wifiConfiguration ? html`
@@ -244,19 +243,19 @@ export class HassioNetwork extends LitElement { > ` - : ""} + : nothing} ` - : ""} + : nothing} ` - : ""} + : nothing} ${this._dirty ? html` ${this.hass.localize( "ui.panel.config.network.supervisor.warning" )} ` - : ""} + : nothing}
@@ -265,11 +264,19 @@ export class HassioNetwork extends LitElement { ` : this.hass.localize("ui.common.save")} + + ${this.hass.localize("ui.common.clear")} +
`; } private _selectAP(event) { this._wifiConfiguration = event.currentTarget.ap; + IP_VERSIONS.forEach((version) => { + if (this._interface![version]!.method === "disabled") { + this._interface![version]!.method = "auto"; + } + }); this._dirty = true; } @@ -279,10 +286,22 @@ export class HassioNetwork extends LitElement { } this._scanning = true; try { - this._accessPoints = await accesspointScan( - this.hass, - this._interface.interface - ); + const aps = await accesspointScan(this.hass, this._interface.interface); + this._accessPoints = []; + aps.accesspoints?.forEach((ap) => { + if (ap.ssid) { + // filter out duplicates + const existing = this._accessPoints.find((a) => a.ssid === ap.ssid); + if (!existing) { + this._accessPoints.push(ap); + } else if (ap.signal > existing.signal) { + this._accessPoints = this._accessPoints.filter( + (a) => a.ssid !== ap.ssid + ); + this._accessPoints.push(ap); + } + } + }); } catch (err: any) { showAlertDialog(this, { title: "Failed to scan for accesspoints", @@ -294,6 +313,10 @@ export class HassioNetwork extends LitElement { } private _renderIPConfiguration(version: string) { + const watingForSSID = + this._interface?.type === "wireless" && + !this._wifiConfiguration?.ssid && + !this._interface.wifi?.ssid; const nameservers = this._interface![version]?.nameservers || []; if (nameservers.length === 0) { nameservers.push(""); // always show input @@ -304,187 +327,203 @@ export class HassioNetwork extends LitElement { .header=${`IPv${version.charAt(version.length - 1)}`} outlined > -
- - - - - - - - - - - - -
- ${["static", "auto"].includes(this._interface![version].method) - ? html` - ${this._interface![version].address.map( - (address: string, index: number) => { - const { ip, mask } = parseAddress(address); - return html` -
- - - - - ${this._interface![version].address.length > 1 && - !disableInputs - ? html` - - ` - : nothing} -
- `; - } + ${watingForSSID + ? html` + ${this.hass.localize( + "ui.panel.config.network.supervisor.waiting_for_ssid" )} - ${!disableInputs + ` + : html`
+ + + + + + + + + + + + +
+ ${["static", "auto"].includes(this._interface![version].method) ? html` - { + const { ip, mask } = parseAddress(address); + return html` +
+ + + + + ${this._interface![version].address.length > 1 && + !disableInputs + ? html` + + ` + : nothing} +
+ `; + } + )} + ${!disableInputs + ? html` + + ${this.hass.localize( + "ui.panel.config.network.supervisor.add_address" + )} + + + ` + : nothing} + - ${this.hass.localize( - "ui.panel.config.network.supervisor.add_address" - )} - -
- ` - : nothing} - - -
- ${nameservers.map( - (nameserver: string, index: number) => html` -
- - - ${this._interface![version].nameservers?.length > 1 - ? html` - +
+ ${nameservers.map( + (nameserver: string, index: number) => html` +
+ - ` - : nothing} + @change=${this._handleInputValueChanged} + > + + ${this._interface![version].nameservers?.length > 1 + ? html` + + ` + : nothing} +
+ ` + )}
- ` - )} -
- - - ${this.hass.localize( - "ui.panel.config.network.supervisor.add_dns_server" - )} - - - ${Object.entries(PREDEFINED_DNS[version]).map( - ([name, addresses]) => html` - - ${name} - + + ${this.hass.localize( + "ui.panel.config.network.supervisor.add_dns_server" + )} + + + ${Object.entries(PREDEFINED_DNS[version]).map( + ([name, addresses]) => html` + + ${name} + + ` + )} + + ${this.hass.localize( + "ui.panel.config.network.supervisor.custom_dns" + )} + + ` - )} - - ${this.hass.localize( - "ui.panel.config.network.supervisor.custom_dns" - )} - - - ` - : ""} + : nothing}`} `; } @@ -529,9 +568,13 @@ export class HassioNetwork extends LitElement { } interfaceOptions.enabled = - this._wifiConfiguration !== undefined || - interfaceOptions.ipv4?.method !== "disabled" || - interfaceOptions.ipv6?.method !== "disabled"; + // at least one ip version is enabled + (interfaceOptions.ipv4?.method !== "disabled" || + interfaceOptions.ipv6?.method !== "disabled") && + // require connection if this is a wireless interface + (this._interface!.type !== "wireless" || + this._wifiConfiguration !== undefined || + !!this._interface!.wifi); try { await updateNetworkInterface( @@ -540,6 +583,7 @@ export class HassioNetwork extends LitElement { interfaceOptions ); this._dirty = false; + await this._fetchNetworkInfo(); } catch (err: any) { showAlertDialog(this, { title: this.hass.localize( @@ -552,6 +596,18 @@ export class HassioNetwork extends LitElement { } } + private async _clear() { + await this._fetchNetworkInfo(); + this._interface!.ipv4!.method = "auto"; + this._interface!.ipv6!.method = "auto"; + // removing the connection will disable the interface + // this is the only way to forget the wifi network right now + this._interface!.wifi = null; + this._wifiConfiguration = undefined; + this._dirty = true; + this.requestUpdate("_interface"); + } + private async _handleTabActivated(ev: CustomEvent): Promise { if (this._dirty) { const confirm = await showConfirmationDialog(this, { @@ -566,6 +622,10 @@ export class HassioNetwork extends LitElement { } this._curTabIndex = ev.detail.index; this._interface = { ...this._interfaces[ev.detail.index] }; + // mock + if (this._interface?.wifi) { + // this._interface!.wifi!.ssid = undefined; + } } private _handleRadioValueChanged(ev: Event): void { diff --git a/src/translations/en.json b/src/translations/en.json index d0a62f91b975..914ed7f40bf3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5241,6 +5241,7 @@ "custom_dns": "Custom", "unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?", "failed_to_change": "Failed to change network settings", + "waiting_for_ssid": "Please select a Wi-Fi network first", "hostname": { "title": "Host name", "description": "The name your instance will have on your network", From 8dafddb05189f5d657207f3f4d4f2d1d50fbe91b Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 22 Oct 2024 12:44:52 +0300 Subject: [PATCH 2/4] some UI tweaks based on comments --- .../config/network/supervisor-network.ts | 375 +++++++++--------- src/translations/en.json | 3 +- 2 files changed, 183 insertions(+), 195 deletions(-) diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts index ed6cf36f742c..defd32220d75 100644 --- a/src/panels/config/network/supervisor-network.ts +++ b/src/panels/config/network/supervisor-network.ts @@ -1,6 +1,6 @@ import "@material/mwc-tab"; import "@material/mwc-tab-bar"; -import { mdiDeleteOutline, mdiPlus, mdiMenuDown } from "@mdi/js"; +import { mdiDeleteOutline, mdiPlus, mdiMenuDown, mdiWifi } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; @@ -121,11 +121,6 @@ export class HassioNetwork extends LitElement { private _renderTab() { return html`
- ${IP_VERSIONS.map((version) => - this._interface![version] - ? this._renderIPConfiguration(version) - : nothing - )} ${this._interface?.type === "wireless" ? html` ${this._interface?.wifi?.ssid ? html`

+ ${this.hass.localize( "ui.panel.config.network.supervisor.connected_to", { ssid: this._interface?.wifi?.ssid } @@ -154,6 +150,7 @@ export class HassioNetwork extends LitElement { : this.hass.localize( "ui.panel.config.network.supervisor.scan_ap" )} + ${this._accessPoints.length ? html` @@ -249,6 +246,11 @@ export class HassioNetwork extends LitElement { ` : nothing} + ${IP_VERSIONS.map((version) => + this._interface![version] + ? this._renderIPConfiguration(version) + : nothing + )} ${this._dirty ? html` ${this.hass.localize( @@ -317,6 +319,9 @@ export class HassioNetwork extends LitElement { this._interface?.type === "wireless" && !this._wifiConfiguration?.ssid && !this._interface.wifi?.ssid; + if (watingForSSID) { + return nothing; + } const nameservers = this._interface![version]?.nameservers || []; if (nameservers.length === 0) { nameservers.push(""); // always show input @@ -327,203 +332,187 @@ export class HassioNetwork extends LitElement { .header=${`IPv${version.charAt(version.length - 1)}`} outlined > - ${watingForSSID - ? html` - ${this.hass.localize( - "ui.panel.config.network.supervisor.waiting_for_ssid" - )} - ` - : html`

- - - - - - - - - - - - -
- ${["static", "auto"].includes(this._interface![version].method) - ? html` - ${this._interface![version].address.map( - (address: string, index: number) => { - const { ip, mask } = parseAddress(address); - return html` -
- - - + + + + + + + + + + + + +
+ ${["static", "auto"].includes(this._interface![version].method) + ? html` + ${this._interface![version].address.map( + (address: string, index: number) => { + const { ip, mask } = parseAddress(address); + return html` +
+ + + + + ${this._interface![version].address.length > 1 && + !disableInputs + ? html` + - - ${this._interface![version].address.length > 1 && - !disableInputs - ? html` - - ` - : nothing} -
- `; - } - )} - ${!disableInputs - ? html` - - ${this.hass.localize( - "ui.panel.config.network.supervisor.add_address" - )} - - - ` - : nothing} - + ` + : nothing} +
+ `; + } + )} + ${!disableInputs + ? html` + - -
- ${nameservers.map( - (nameserver: string, index: number) => html` -
- + + ` + : nothing} + + +
+ ${nameservers.map( + (nameserver: string, index: number) => html` +
+ + + ${this._interface![version].nameservers?.length > 1 + ? html` + - - ${this._interface![version].nameservers?.length > 1 - ? html` - - ` - : nothing} -
- ` - )} + @click=${this._removeNameserver} + > + ` + : nothing}
- + + + ${this.hass.localize( + "ui.panel.config.network.supervisor.add_dns_server" + )} + + + ${Object.entries(PREDEFINED_DNS[version]).map( + ([name, addresses]) => html` + - - ${this.hass.localize( - "ui.panel.config.network.supervisor.add_dns_server" - )} - - - ${Object.entries(PREDEFINED_DNS[version]).map( - ([name, addresses]) => html` - - ${name} - - ` - )} - - ${this.hass.localize( - "ui.panel.config.network.supervisor.custom_dns" - )} - - + ${name} + ` - : nothing}`} + )} + + ${this.hass.localize( + "ui.panel.config.network.supervisor.custom_dns" + )} + + + ` + : nothing} `; } diff --git a/src/translations/en.json b/src/translations/en.json index 914ed7f40bf3..917ff0ef4e91 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5221,7 +5221,7 @@ "supervisor": { "title": "Configure network interfaces", "connected_to": "Connected to {ssid}", - "scan_ap": "Scan for access points", + "scan_ap": "Search networks", "signal_strength": "Signal strength", "open": "Open", "wep": "WEP", @@ -5241,7 +5241,6 @@ "custom_dns": "Custom", "unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?", "failed_to_change": "Failed to change network settings", - "waiting_for_ssid": "Please select a Wi-Fi network first", "hostname": { "title": "Host name", "description": "The name your instance will have on your network", From c4a0b59a50458932c75b3cda34d867ad6cb4abdb Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Wed, 23 Oct 2024 07:57:14 +0300 Subject: [PATCH 3/4] change label and remove DNS on reset --- src/panels/config/network/supervisor-network.ts | 4 +++- src/translations/en.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts index defd32220d75..41716b6640d0 100644 --- a/src/panels/config/network/supervisor-network.ts +++ b/src/panels/config/network/supervisor-network.ts @@ -267,7 +267,7 @@ export class HassioNetwork extends LitElement { : this.hass.localize("ui.common.save")} - ${this.hass.localize("ui.common.clear")} + ${this.hass.localize("ui.panel.config.network.supervisor.reset")}
`; } @@ -588,7 +588,9 @@ export class HassioNetwork extends LitElement { private async _clear() { await this._fetchNetworkInfo(); this._interface!.ipv4!.method = "auto"; + this._interface!.ipv4!.nameservers = []; this._interface!.ipv6!.method = "auto"; + this._interface!.ipv6!.nameservers = []; // removing the connection will disable the interface // this is the only way to forget the wifi network right now this._interface!.wifi = null; diff --git a/src/translations/en.json b/src/translations/en.json index 917ff0ef4e91..7795127dc668 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5222,6 +5222,7 @@ "title": "Configure network interfaces", "connected_to": "Connected to {ssid}", "scan_ap": "Search networks", + "reset": "Reset configuration", "signal_strength": "Signal strength", "open": "Open", "wep": "WEP", From f65febb35525c7aedb5db944d906a943a88798f8 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Thu, 24 Oct 2024 11:08:27 +0300 Subject: [PATCH 4/4] remove mock code --- src/panels/config/network/supervisor-network.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts index 41716b6640d0..0d2fcfaf2134 100644 --- a/src/panels/config/network/supervisor-network.ts +++ b/src/panels/config/network/supervisor-network.ts @@ -613,10 +613,6 @@ export class HassioNetwork extends LitElement { } this._curTabIndex = ev.detail.index; this._interface = { ...this._interfaces[ev.detail.index] }; - // mock - if (this._interface?.wifi) { - // this._interface!.wifi!.ssid = undefined; - } } private _handleRadioValueChanged(ev: Event): void {