mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Improve IP configuration UI (#22320)
* Split netmask from IP address * handle ipv6 as well * render fix * improved UI for IP, mask & DNS * remove ip detail dialog * use `nothing` Co-authored-by: Paul Bottein <paul.bottein@gmail.com> * use ha-list-item instead of mwc --------- Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
parent
e2a89a55b7
commit
202bc6440b
@ -4,7 +4,7 @@ import { hassioApiResultExtractor, HassioResponse } from "./common";
|
|||||||
|
|
||||||
interface IpConfiguration {
|
interface IpConfiguration {
|
||||||
address: string[];
|
address: string[];
|
||||||
gateway: string;
|
gateway: string | null;
|
||||||
method: "disabled" | "static" | "auto";
|
method: "disabled" | "static" | "auto";
|
||||||
nameservers: string[];
|
nameservers: string[];
|
||||||
}
|
}
|
||||||
@ -114,3 +114,65 @@ export const accesspointScan = async (
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseAddress = (address: string) => {
|
||||||
|
const [ip, cidr] = address.split("/");
|
||||||
|
return { ip, mask: cidrToNetmask(cidr, address.includes(":")) };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatAddress = (ip: string, mask: string) =>
|
||||||
|
`${ip}/${netmaskToCidr(mask)}`;
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
export const cidrToNetmask = (
|
||||||
|
cidr: string,
|
||||||
|
isIPv6: boolean = false
|
||||||
|
): string => {
|
||||||
|
const bits = parseInt(cidr, 10);
|
||||||
|
if (isIPv6) {
|
||||||
|
const fullMask = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff";
|
||||||
|
const numGroups = Math.floor(bits / 16);
|
||||||
|
const remainingBits = bits % 16;
|
||||||
|
const lastGroup = remainingBits
|
||||||
|
? parseInt(
|
||||||
|
"1".repeat(remainingBits) + "0".repeat(16 - remainingBits),
|
||||||
|
2
|
||||||
|
).toString(16)
|
||||||
|
: "";
|
||||||
|
return fullMask
|
||||||
|
.split(":")
|
||||||
|
.slice(0, numGroups)
|
||||||
|
.concat(lastGroup)
|
||||||
|
.concat(Array(8 - numGroups - (lastGroup ? 1 : 0)).fill("0"))
|
||||||
|
.join(":");
|
||||||
|
}
|
||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
const mask = ~(2 ** (32 - bits) - 1);
|
||||||
|
return [
|
||||||
|
(mask >>> 24) & 255,
|
||||||
|
(mask >>> 16) & 255,
|
||||||
|
(mask >>> 8) & 255,
|
||||||
|
mask & 255,
|
||||||
|
].join(".");
|
||||||
|
/* eslint-enable no-bitwise */
|
||||||
|
};
|
||||||
|
|
||||||
|
export const netmaskToCidr = (netmask: string): number => {
|
||||||
|
if (netmask.includes(":")) {
|
||||||
|
// IPv6
|
||||||
|
return netmask
|
||||||
|
.split(":")
|
||||||
|
.map((group) =>
|
||||||
|
group ? (parseInt(group, 16).toString(2).match(/1/g) || []).length : 0
|
||||||
|
)
|
||||||
|
.reduce((sum, val) => sum + val, 0);
|
||||||
|
}
|
||||||
|
// IPv4
|
||||||
|
return netmask
|
||||||
|
.split(".")
|
||||||
|
.reduce(
|
||||||
|
(count, octet) =>
|
||||||
|
count + (parseInt(octet, 10).toString(2).match(/1/g) || []).length,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
|
||||||
import { CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
|
||||||
import type { NetworkInterface } from "../../../data/hassio/network";
|
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
|
||||||
import type { IPDetailDialogParams } from "./show-ip-detail-dialog";
|
|
||||||
|
|
||||||
@customElement("dialog-ip-detail")
|
|
||||||
class DialogIPDetail extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _params?: IPDetailDialogParams;
|
|
||||||
|
|
||||||
@state() private _interface?: NetworkInterface;
|
|
||||||
|
|
||||||
public showDialog(params: IPDetailDialogParams): void {
|
|
||||||
this._params = params;
|
|
||||||
this._interface = this._params.interface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog() {
|
|
||||||
this._params = undefined;
|
|
||||||
this._interface = undefined;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this._interface) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipv4 = this._interface.ipv4;
|
|
||||||
const ipv6 = this._interface.ipv6;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-dialog
|
|
||||||
open
|
|
||||||
@closed=${this.closeDialog}
|
|
||||||
scrimClickAction
|
|
||||||
escapeKeyAction
|
|
||||||
.heading=${createCloseHeading(
|
|
||||||
this.hass,
|
|
||||||
this.hass.localize("ui.dialogs.dialog-ip-detail.ip_information")
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
${ipv4
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
${this.hass.localize("ui.dialogs.dialog-ip-detail.ipv4")}
|
|
||||||
</h3>
|
|
||||||
${ipv4.address
|
|
||||||
? html`<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.ip_address",
|
|
||||||
{ address: ipv4.address?.join(", ") }
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
${ipv4.gateway
|
|
||||||
? html`<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.gateway",
|
|
||||||
{ gateway: ipv4.gateway }
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
${ipv4.method
|
|
||||||
? html`<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.method",
|
|
||||||
{ method: ipv4.method }
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
${ipv4.nameservers?.length
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.nameservers",
|
|
||||||
{ nameservers: ipv4.nameservers?.join(", ") }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${ipv6
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
${this.hass.localize("ui.dialogs.dialog-ip-detail.ipv6")}
|
|
||||||
</h3>
|
|
||||||
${ipv6.address
|
|
||||||
? html`<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.ip_address",
|
|
||||||
{ address: ipv6.address?.join(", ") }
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
${ipv6.gateway
|
|
||||||
? html`<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.gateway",
|
|
||||||
{ gateway: ipv6.gateway }
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
${ipv6.method
|
|
||||||
? html`<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.method",
|
|
||||||
{ method: ipv6.method }
|
|
||||||
)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
${ipv6.nameservers?.length
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.dialogs.dialog-ip-detail.nameservers",
|
|
||||||
{ nameservers: ipv6.nameservers?.join(", ") }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</ha-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles: CSSResultGroup = haStyleDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"dialog-ip-detail": DialogIPDetail;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import type { NetworkInterface } from "../../../data/hassio/network";
|
|
||||||
|
|
||||||
export interface IPDetailDialogParams {
|
|
||||||
interface?: NetworkInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadIPDetailDialog = () => import("./dialog-ip-detail");
|
|
||||||
|
|
||||||
export const showIPDetailDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
dialogParams: IPDetailDialogParams
|
|
||||||
): void => {
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "dialog-ip-detail",
|
|
||||||
dialogImport: loadIPDetailDialog,
|
|
||||||
dialogParams,
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,13 +1,11 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
|
||||||
import { ActionDetail } from "@material/mwc-list/mwc-list";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@material/mwc-tab";
|
import "@material/mwc-tab";
|
||||||
import "@material/mwc-tab-bar";
|
import "@material/mwc-tab-bar";
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDeleteOutline, mdiPlus, mdiMenuDown } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { cache } from "lit/directives/cache";
|
import { cache } from "lit/directives/cache";
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
|
import "../../../components/ha-button";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
@ -16,6 +14,7 @@ import "../../../components/ha-formfield";
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-password-field";
|
import "../../../components/ha-password-field";
|
||||||
import "../../../components/ha-radio";
|
import "../../../components/ha-radio";
|
||||||
|
import "../../../components/ha-list-item";
|
||||||
import type { HaRadio } from "../../../components/ha-radio";
|
import type { HaRadio } from "../../../components/ha-radio";
|
||||||
import "../../../components/ha-textfield";
|
import "../../../components/ha-textfield";
|
||||||
import type { HaTextField } from "../../../components/ha-textfield";
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
@ -24,7 +23,9 @@ import {
|
|||||||
AccessPoints,
|
AccessPoints,
|
||||||
accesspointScan,
|
accesspointScan,
|
||||||
fetchNetworkInfo,
|
fetchNetworkInfo,
|
||||||
|
formatAddress,
|
||||||
NetworkInterface,
|
NetworkInterface,
|
||||||
|
parseAddress,
|
||||||
updateNetworkInterface,
|
updateNetworkInterface,
|
||||||
WifiConfiguration,
|
WifiConfiguration,
|
||||||
} from "../../../data/hassio/network";
|
} from "../../../data/hassio/network";
|
||||||
@ -33,10 +34,26 @@ import {
|
|||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
} from "../../../dialogs/generic/show-dialog-box";
|
} from "../../../dialogs/generic/show-dialog-box";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { showIPDetailDialog } from "./show-ip-detail-dialog";
|
|
||||||
|
|
||||||
const IP_VERSIONS = ["ipv4", "ipv6"];
|
const IP_VERSIONS = ["ipv4", "ipv6"];
|
||||||
|
|
||||||
|
const PREDEFINED_DNS = {
|
||||||
|
ipv4: {
|
||||||
|
Cloudflare: ["1.1.1.1", "1.0.0.1"],
|
||||||
|
Google: ["8.8.8.8", "8.8.4.4"],
|
||||||
|
Quad9: ["9.9.9.9", "149.112.112.112"],
|
||||||
|
NextDNS: ["45.90.28.0", "45.90.30.0"],
|
||||||
|
AdGuard: ["94.140.14.140", "94.140.14.141"],
|
||||||
|
},
|
||||||
|
ipv6: {
|
||||||
|
Cloudflare: ["2606:4700:4700::1111", "2606:4700:4700::1001"],
|
||||||
|
Google: ["2001:4860:4860::8888", "2001:4860:4860::8844"],
|
||||||
|
Quad9: ["2620:fe::fe", "2620:fe::9"],
|
||||||
|
NextDNS: ["2a05:d014:a000::", "2a05:d014:a001::"],
|
||||||
|
AdGuard: ["94.140.14.140", "94.140.14.141"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("supervisor-network")
|
@customElement("supervisor-network")
|
||||||
export class HassioNetwork extends LitElement {
|
export class HassioNetwork extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -57,6 +74,8 @@ export class HassioNetwork extends LitElement {
|
|||||||
|
|
||||||
@state() private _wifiConfiguration?: WifiConfiguration;
|
@state() private _wifiConfiguration?: WifiConfiguration;
|
||||||
|
|
||||||
|
@state() private _dnsMenuOpen = false;
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this._fetchNetworkInfo();
|
this._fetchNetworkInfo();
|
||||||
}
|
}
|
||||||
@ -121,7 +140,7 @@ export class HassioNetwork extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</p>`
|
</p>`
|
||||||
: ""}
|
: ""}
|
||||||
<mwc-button
|
<ha-button
|
||||||
class="scan"
|
class="scan"
|
||||||
@click=${this._scanForAP}
|
@click=${this._scanForAP}
|
||||||
.disabled=${this._scanning}
|
.disabled=${this._scanning}
|
||||||
@ -132,7 +151,7 @@ export class HassioNetwork extends LitElement {
|
|||||||
: this.hass.localize(
|
: this.hass.localize(
|
||||||
"ui.panel.config.network.supervisor.scan_ap"
|
"ui.panel.config.network.supervisor.scan_ap"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</ha-button>
|
||||||
${this._accessPoints &&
|
${this._accessPoints &&
|
||||||
this._accessPoints.accesspoints &&
|
this._accessPoints.accesspoints &&
|
||||||
this._accessPoints.accesspoints.length !== 0
|
this._accessPoints.accesspoints.length !== 0
|
||||||
@ -142,7 +161,7 @@ export class HassioNetwork extends LitElement {
|
|||||||
.filter((ap) => ap.ssid)
|
.filter((ap) => ap.ssid)
|
||||||
.map(
|
.map(
|
||||||
(ap) => html`
|
(ap) => html`
|
||||||
<mwc-list-item
|
<ha-list-item
|
||||||
twoline
|
twoline
|
||||||
@click=${this._selectAP}
|
@click=${this._selectAP}
|
||||||
.activated=${ap.ssid ===
|
.activated=${ap.ssid ===
|
||||||
@ -157,7 +176,7 @@ export class HassioNetwork extends LitElement {
|
|||||||
)}:
|
)}:
|
||||||
${ap.signal}
|
${ap.signal}
|
||||||
</span>
|
</span>
|
||||||
</mwc-list-item>
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
@ -240,35 +259,15 @@ export class HassioNetwork extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
<ha-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||||
${this._processing
|
${this._processing
|
||||||
? html`<ha-circular-progress indeterminate size="small">
|
? html`<ha-circular-progress indeterminate size="small">
|
||||||
</ha-circular-progress>`
|
</ha-circular-progress>`
|
||||||
: this.hass.localize("ui.common.save")}
|
: this.hass.localize("ui.common.save")}
|
||||||
</mwc-button>
|
</ha-button>
|
||||||
<ha-button-menu @action=${this._handleAction}>
|
|
||||||
<ha-icon-button
|
|
||||||
slot="trigger"
|
|
||||||
.label=${"ui.common.menu"}
|
|
||||||
.path=${mdiDotsVertical}
|
|
||||||
></ha-icon-button>
|
|
||||||
<mwc-list-item
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.network.ip_information"
|
|
||||||
)}</mwc-list-item
|
|
||||||
>
|
|
||||||
</ha-button-menu>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
|
||||||
switch (ev.detail.index) {
|
|
||||||
case 0:
|
|
||||||
showIPDetailDialog(this, { interface: this._interface });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _selectAP(event) {
|
private _selectAP(event) {
|
||||||
this._wifiConfiguration = event.currentTarget.ap;
|
this._wifiConfiguration = event.currentTarget.ap;
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
@ -295,6 +294,11 @@ export class HassioNetwork extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderIPConfiguration(version: string) {
|
private _renderIPConfiguration(version: string) {
|
||||||
|
const nameservers = this._interface![version]?.nameservers || [];
|
||||||
|
if (nameservers.length === 0) {
|
||||||
|
nameservers.push(""); // always show input
|
||||||
|
}
|
||||||
|
const disableInputs = this._interface![version]?.method === "auto";
|
||||||
return html`
|
return html`
|
||||||
<ha-expansion-panel
|
<ha-expansion-panel
|
||||||
.header=${`IPv${version.charAt(version.length - 1)}`}
|
.header=${`IPv${version.charAt(version.length - 1)}`}
|
||||||
@ -345,69 +349,146 @@ export class HassioNetwork extends LitElement {
|
|||||||
</ha-radio>
|
</ha-radio>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
</div>
|
</div>
|
||||||
${this._interface![version].method === "static"
|
${["static", "auto"].includes(this._interface![version].method)
|
||||||
? html`
|
? html`
|
||||||
<ha-textfield
|
${this._interface![version].address.map(
|
||||||
id="address"
|
(address: string, index: number) => {
|
||||||
.label=${this.hass.localize(
|
const { ip, mask } = parseAddress(address);
|
||||||
"ui.panel.config.network.supervisor.ip_netmask"
|
return html`
|
||||||
)}
|
<div class="address-row">
|
||||||
.version=${version}
|
<ha-textfield
|
||||||
.value=${this._toString(this._interface![version].address)}
|
id="address"
|
||||||
@change=${this._handleInputValueChanged}
|
.label=${this.hass.localize(
|
||||||
>
|
"ui.panel.config.network.supervisor.ip"
|
||||||
</ha-textfield>
|
)}
|
||||||
|
.version=${version}
|
||||||
|
.value=${ip}
|
||||||
|
.index=${index}
|
||||||
|
@change=${this._handleInputValueChanged}
|
||||||
|
.disabled=${disableInputs}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
id="netmask"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.network.supervisor.netmask"
|
||||||
|
)}
|
||||||
|
.version=${version}
|
||||||
|
.value=${mask}
|
||||||
|
.index=${index}
|
||||||
|
@change=${this._handleInputValueChanged}
|
||||||
|
.disabled=${disableInputs}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
${this._interface![version].address.length > 1 &&
|
||||||
|
!disableInputs
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.common.delete")}
|
||||||
|
.path=${mdiDeleteOutline}
|
||||||
|
.version=${version}
|
||||||
|
.index=${index}
|
||||||
|
@click=${this._removeAddress}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
${!disableInputs
|
||||||
|
? html`
|
||||||
|
<ha-button
|
||||||
|
@click=${this._addAddress}
|
||||||
|
.version=${version}
|
||||||
|
class="add-address"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.network.supervisor.add_address"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</ha-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
id="gateway"
|
id="gateway"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.network.supervisor.gateway"
|
"ui.panel.config.network.supervisor.gateway"
|
||||||
)}
|
)}
|
||||||
.version=${version}
|
.version=${version}
|
||||||
.value=${this._interface![version].gateway}
|
.value=${this._interface![version].gateway || ""}
|
||||||
@change=${this._handleInputValueChanged}
|
@change=${this._handleInputValueChanged}
|
||||||
|
.disabled=${disableInputs}
|
||||||
>
|
>
|
||||||
</ha-textfield>
|
</ha-textfield>
|
||||||
<ha-textfield
|
<div class="nameservers">
|
||||||
id="nameservers"
|
${nameservers.map(
|
||||||
.label=${this.hass.localize(
|
(nameserver: string, index: number) => html`
|
||||||
"ui.panel.config.network.supervisor.dns_servers"
|
<div class="address-row">
|
||||||
|
<ha-textfield
|
||||||
|
id="nameserver"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.network.supervisor.dns_server"
|
||||||
|
)}
|
||||||
|
.version=${version}
|
||||||
|
.value=${nameserver}
|
||||||
|
.index=${index}
|
||||||
|
@change=${this._handleInputValueChanged}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
${this._interface![version].nameservers?.length > 1
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.common.delete")}
|
||||||
|
.path=${mdiDeleteOutline}
|
||||||
|
.version=${version}
|
||||||
|
.index=${index}
|
||||||
|
@click=${this._removeNameserver}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
<ha-button-menu
|
||||||
|
@opened=${this._handleDNSMenuOpened}
|
||||||
|
@closed=${this._handleDNSMenuClosed}
|
||||||
.version=${version}
|
.version=${version}
|
||||||
.value=${this._toString(this._interface![version].nameservers)}
|
class="add-nameserver"
|
||||||
@change=${this._handleInputValueChanged}
|
|
||||||
>
|
>
|
||||||
</ha-textfield>
|
<ha-button slot="trigger">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.network.supervisor.add_dns_server"
|
||||||
|
)}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${this._dnsMenuOpen ? mdiMenuDown : mdiPlus}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-button>
|
||||||
|
${Object.entries(PREDEFINED_DNS[version]).map(
|
||||||
|
([name, addresses]) => html`
|
||||||
|
<ha-list-item
|
||||||
|
@click=${this._addPredefinedDNS}
|
||||||
|
.version=${version}
|
||||||
|
.addresses=${addresses}
|
||||||
|
>
|
||||||
|
${name}
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<ha-list-item @click=${this._addCustomDNS} .version=${version}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.network.supervisor.custom_dns"
|
||||||
|
)}
|
||||||
|
</ha-list-item>
|
||||||
|
</ha-button-menu>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</ha-expansion-panel>
|
</ha-expansion-panel>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_toArray(data: string | string[]): string[] {
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
if (data && typeof data[0] === "string") {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (typeof data === "string") {
|
|
||||||
return data.replace(/ /g, "").split(",");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toString(data: string | string[]): string {
|
|
||||||
if (!data) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
return data.join(", ");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _updateNetwork() {
|
private async _updateNetwork() {
|
||||||
this._processing = true;
|
this._processing = true;
|
||||||
let interfaceOptions: Partial<NetworkInterface> = {};
|
let interfaceOptions: Partial<NetworkInterface> = {};
|
||||||
@ -419,9 +500,13 @@ export class HassioNetwork extends LitElement {
|
|||||||
if (this._interface![version]?.method === "static") {
|
if (this._interface![version]?.method === "static") {
|
||||||
interfaceOptions[version] = {
|
interfaceOptions[version] = {
|
||||||
...interfaceOptions[version],
|
...interfaceOptions[version],
|
||||||
address: this._toArray(this._interface![version]?.address),
|
address: this._interface![version]?.address?.filter(
|
||||||
|
(address: string) => address.trim()
|
||||||
|
),
|
||||||
gateway: this._interface![version]?.gateway,
|
gateway: this._interface![version]?.gateway,
|
||||||
nameservers: this._toArray(this._interface![version]?.nameservers),
|
nameservers: this._interface![version]?.nameservers?.filter(
|
||||||
|
(ns: string) => ns.trim()
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -515,16 +600,28 @@ export class HassioNetwork extends LitElement {
|
|||||||
const version = (ev.target as any).version as "ipv4" | "ipv6";
|
const version = (ev.target as any).version as "ipv4" | "ipv6";
|
||||||
const id = source.id;
|
const id = source.id;
|
||||||
|
|
||||||
if (
|
if (!value || !this._interface?.[version]) {
|
||||||
!value ||
|
|
||||||
!this._interface ||
|
|
||||||
this._toString(this._interface[version]![id]) === this._toString(value)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
this._interface[version]![id] = value;
|
if (id === "address") {
|
||||||
|
const index = (ev.target as any).index as number;
|
||||||
|
const { mask } = parseAddress(value);
|
||||||
|
this._interface[version]!.address![index] = formatAddress(value, mask);
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
} else if (id === "netmask") {
|
||||||
|
const index = (ev.target as any).index as number;
|
||||||
|
const { ip } = parseAddress(this._interface![version]!.address![index]);
|
||||||
|
this._interface[version]!.address![index] = formatAddress(ip, value);
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
} else if (id === "nameserver") {
|
||||||
|
const index = (ev.target as any).index as number;
|
||||||
|
this._interface[version]!.nameservers![index] = value;
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
} else {
|
||||||
|
this._interface[version]![id] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleInputValueChangedWifi(ev: Event): void {
|
private _handleInputValueChangedWifi(ev: Event): void {
|
||||||
@ -543,6 +640,64 @@ export class HassioNetwork extends LitElement {
|
|||||||
this._wifiConfiguration![id] = value;
|
this._wifiConfiguration![id] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addAddress(ev: Event): void {
|
||||||
|
const version = (ev.target as any).version as "ipv4" | "ipv6";
|
||||||
|
this._interface![version]!.address!.push(
|
||||||
|
version === "ipv4" ? "0.0.0.0/24" : "::/64"
|
||||||
|
);
|
||||||
|
this._dirty = true;
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeAddress(ev: Event): void {
|
||||||
|
const source = ev.target as any;
|
||||||
|
const index = source.index as number;
|
||||||
|
const version = source.version as "ipv4" | "ipv6";
|
||||||
|
this._interface![version]!.address!.splice(index, 1);
|
||||||
|
this._dirty = true;
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDNSMenuOpened() {
|
||||||
|
this._dnsMenuOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDNSMenuClosed() {
|
||||||
|
this._dnsMenuOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addPredefinedDNS(ev: Event) {
|
||||||
|
const source = ev.target as any;
|
||||||
|
const version = source.version as "ipv4" | "ipv6";
|
||||||
|
const addresses = source.addresses as string[];
|
||||||
|
if (!this._interface![version]!.nameservers) {
|
||||||
|
this._interface![version]!.nameservers = [];
|
||||||
|
}
|
||||||
|
this._interface![version]!.nameservers!.push(...addresses);
|
||||||
|
this._dirty = true;
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addCustomDNS(ev: Event) {
|
||||||
|
const source = ev.target as any;
|
||||||
|
const version = source.version as "ipv4" | "ipv6";
|
||||||
|
if (!this._interface![version]!.nameservers) {
|
||||||
|
this._interface![version]!.nameservers = [];
|
||||||
|
}
|
||||||
|
this._interface![version]!.nameservers!.push("");
|
||||||
|
this._dirty = true;
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeNameserver(ev: Event): void {
|
||||||
|
const source = ev.target as any;
|
||||||
|
const index = source.index as number;
|
||||||
|
const version = source.version as "ipv4" | "ipv6";
|
||||||
|
this._interface![version]!.nameservers!.splice(index, 1);
|
||||||
|
this._dirty = true;
|
||||||
|
this.requestUpdate("_interface");
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
@ -557,11 +712,11 @@ export class HassioNetwork extends LitElement {
|
|||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-button.warning {
|
ha-button.warning {
|
||||||
--mdc-theme-primary: var(--error-color);
|
--mdc-theme-primary: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-button.scan {
|
ha-button.scan {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-inline-start: 8px;
|
margin-inline-start: 8px;
|
||||||
margin-inline-end: initial;
|
margin-inline-end: initial;
|
||||||
@ -574,10 +729,24 @@ export class HassioNetwork extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
ha-expansion-panel ha-textfield:last-child {
|
.address-row {
|
||||||
margin-bottom: 16px;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
mwc-list-item {
|
.address-row ha-textfield {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.address-row ha-icon-button {
|
||||||
|
--mdc-icon-button-size: 36px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.add-address,
|
||||||
|
.add-nameserver {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
ha-list-item {
|
||||||
--mdc-list-side-padding: 10px;
|
--mdc-list-side-padding: 10px;
|
||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
@ -586,6 +755,9 @@ export class HassioNetwork extends LitElement {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
ha-expansion-panel > :last-child {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1708,15 +1708,6 @@
|
|||||||
"message_placeholder": "Enter a sentence to speak.",
|
"message_placeholder": "Enter a sentence to speak.",
|
||||||
"play": "Play"
|
"play": "Play"
|
||||||
},
|
},
|
||||||
"dialog-ip-detail": {
|
|
||||||
"ip_information": "[%key:ui::panel::config::network::ip_information%]",
|
|
||||||
"ipv4": "IPv4",
|
|
||||||
"ipv6": "IPv6",
|
|
||||||
"ip_address": "IP Address: {address}",
|
|
||||||
"gateway": "Gateway: {gateway}",
|
|
||||||
"method": "Method: {method}",
|
|
||||||
"nameservers": "Name Servers: {nameservers}"
|
|
||||||
},
|
|
||||||
"update_backup": {
|
"update_backup": {
|
||||||
"title": "Create backup?",
|
"title": "Create backup?",
|
||||||
"text": "This will create a backup before installing.",
|
"text": "This will create a backup before installing.",
|
||||||
@ -5240,9 +5231,13 @@
|
|||||||
"static": "Static",
|
"static": "Static",
|
||||||
"auto": "Automatic",
|
"auto": "Automatic",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"ip_netmask": "IP address/Netmask",
|
"ip": "IP address",
|
||||||
|
"netmask": "Netmask",
|
||||||
|
"add_address": "Add address",
|
||||||
"gateway": "Gateway address",
|
"gateway": "Gateway address",
|
||||||
"dns_servers": "DNS Servers",
|
"dns_server": "DNS Server",
|
||||||
|
"add_dns_server": "Add DNS Server",
|
||||||
|
"custom_dns": "Custom",
|
||||||
"unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
"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",
|
"failed_to_change": "Failed to change network settings",
|
||||||
"hostname": {
|
"hostname": {
|
||||||
@ -7726,6 +7721,7 @@
|
|||||||
"auto": "Automatic",
|
"auto": "Automatic",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"ip_netmask": "IP address/netmask",
|
"ip_netmask": "IP address/netmask",
|
||||||
|
"netmask": "Netmask",
|
||||||
"gateway": "Gateway address",
|
"gateway": "Gateway address",
|
||||||
"dns_servers": "DNS servers",
|
"dns_servers": "DNS servers",
|
||||||
"unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
"unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user