mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 08:16:36 +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 {
|
||||
address: string[];
|
||||
gateway: string;
|
||||
gateway: string | null;
|
||||
method: "disabled" | "static" | "auto";
|
||||
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-bar";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { mdiDeleteOutline, mdiPlus, mdiMenuDown } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-circular-progress";
|
||||
@ -16,6 +14,7 @@ import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-password-field";
|
||||
import "../../../components/ha-radio";
|
||||
import "../../../components/ha-list-item";
|
||||
import type { HaRadio } from "../../../components/ha-radio";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
@ -24,7 +23,9 @@ import {
|
||||
AccessPoints,
|
||||
accesspointScan,
|
||||
fetchNetworkInfo,
|
||||
formatAddress,
|
||||
NetworkInterface,
|
||||
parseAddress,
|
||||
updateNetworkInterface,
|
||||
WifiConfiguration,
|
||||
} from "../../../data/hassio/network";
|
||||
@ -33,10 +34,26 @@ import {
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showIPDetailDialog } from "./show-ip-detail-dialog";
|
||||
|
||||
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")
|
||||
export class HassioNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -57,6 +74,8 @@ export class HassioNetwork extends LitElement {
|
||||
|
||||
@state() private _wifiConfiguration?: WifiConfiguration;
|
||||
|
||||
@state() private _dnsMenuOpen = false;
|
||||
|
||||
protected firstUpdated() {
|
||||
this._fetchNetworkInfo();
|
||||
}
|
||||
@ -121,7 +140,7 @@ export class HassioNetwork extends LitElement {
|
||||
)}
|
||||
</p>`
|
||||
: ""}
|
||||
<mwc-button
|
||||
<ha-button
|
||||
class="scan"
|
||||
@click=${this._scanForAP}
|
||||
.disabled=${this._scanning}
|
||||
@ -132,7 +151,7 @@ export class HassioNetwork extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.scan_ap"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-button>
|
||||
${this._accessPoints &&
|
||||
this._accessPoints.accesspoints &&
|
||||
this._accessPoints.accesspoints.length !== 0
|
||||
@ -142,7 +161,7 @@ export class HassioNetwork extends LitElement {
|
||||
.filter((ap) => ap.ssid)
|
||||
.map(
|
||||
(ap) => html`
|
||||
<mwc-list-item
|
||||
<ha-list-item
|
||||
twoline
|
||||
@click=${this._selectAP}
|
||||
.activated=${ap.ssid ===
|
||||
@ -157,7 +176,7 @@ export class HassioNetwork extends LitElement {
|
||||
)}:
|
||||
${ap.signal}
|
||||
</span>
|
||||
</mwc-list-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
@ -240,35 +259,15 @@ export class HassioNetwork extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
<ha-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.hass.localize("ui.common.save")}
|
||||
</mwc-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>
|
||||
</ha-button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
showIPDetailDialog(this, { interface: this._interface });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _selectAP(event) {
|
||||
this._wifiConfiguration = event.currentTarget.ap;
|
||||
this._dirty = true;
|
||||
@ -295,6 +294,11 @@ export class HassioNetwork extends LitElement {
|
||||
}
|
||||
|
||||
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`
|
||||
<ha-expansion-panel
|
||||
.header=${`IPv${version.charAt(version.length - 1)}`}
|
||||
@ -345,69 +349,146 @@ export class HassioNetwork extends LitElement {
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${this._interface![version].method === "static"
|
||||
${["static", "auto"].includes(this._interface![version].method)
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="address"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.ip_netmask"
|
||||
)}
|
||||
.version=${version}
|
||||
.value=${this._toString(this._interface![version].address)}
|
||||
@change=${this._handleInputValueChanged}
|
||||
>
|
||||
</ha-textfield>
|
||||
${this._interface![version].address.map(
|
||||
(address: string, index: number) => {
|
||||
const { ip, mask } = parseAddress(address);
|
||||
return html`
|
||||
<div class="address-row">
|
||||
<ha-textfield
|
||||
id="address"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.ip"
|
||||
)}
|
||||
.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
|
||||
id="gateway"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.gateway"
|
||||
)}
|
||||
.version=${version}
|
||||
.value=${this._interface![version].gateway}
|
||||
.value=${this._interface![version].gateway || ""}
|
||||
@change=${this._handleInputValueChanged}
|
||||
.disabled=${disableInputs}
|
||||
>
|
||||
</ha-textfield>
|
||||
<ha-textfield
|
||||
id="nameservers"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.dns_servers"
|
||||
<div class="nameservers">
|
||||
${nameservers.map(
|
||||
(nameserver: string, index: number) => html`
|
||||
<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}
|
||||
.value=${this._toString(this._interface![version].nameservers)}
|
||||
@change=${this._handleInputValueChanged}
|
||||
class="add-nameserver"
|
||||
>
|
||||
</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>
|
||||
`;
|
||||
}
|
||||
|
||||
_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() {
|
||||
this._processing = true;
|
||||
let interfaceOptions: Partial<NetworkInterface> = {};
|
||||
@ -419,9 +500,13 @@ export class HassioNetwork extends LitElement {
|
||||
if (this._interface![version]?.method === "static") {
|
||||
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,
|
||||
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 id = source.id;
|
||||
|
||||
if (
|
||||
!value ||
|
||||
!this._interface ||
|
||||
this._toString(this._interface[version]![id]) === this._toString(value)
|
||||
) {
|
||||
if (!value || !this._interface?.[version]) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -543,6 +640,64 @@ export class HassioNetwork extends LitElement {
|
||||
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 {
|
||||
return [
|
||||
css`
|
||||
@ -557,11 +712,11 @@ export class HassioNetwork extends LitElement {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
mwc-button.warning {
|
||||
ha-button.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
mwc-button.scan {
|
||||
ha-button.scan {
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
@ -574,10 +729,24 @@ export class HassioNetwork extends LitElement {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-expansion-panel ha-textfield:last-child {
|
||||
margin-bottom: 16px;
|
||||
.address-row {
|
||||
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;
|
||||
}
|
||||
.card-actions {
|
||||
@ -586,6 +755,9 @@ export class HassioNetwork extends LitElement {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
ha-expansion-panel > :last-child {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -1708,15 +1708,6 @@
|
||||
"message_placeholder": "Enter a sentence to speak.",
|
||||
"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": {
|
||||
"title": "Create backup?",
|
||||
"text": "This will create a backup before installing.",
|
||||
@ -5240,9 +5231,13 @@
|
||||
"static": "Static",
|
||||
"auto": "Automatic",
|
||||
"disabled": "Disabled",
|
||||
"ip_netmask": "IP address/Netmask",
|
||||
"ip": "IP address",
|
||||
"netmask": "Netmask",
|
||||
"add_address": "Add 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?",
|
||||
"failed_to_change": "Failed to change network settings",
|
||||
"hostname": {
|
||||
@ -7726,6 +7721,7 @@
|
||||
"auto": "Automatic",
|
||||
"disabled": "Disabled",
|
||||
"ip_netmask": "IP address/netmask",
|
||||
"netmask": "Netmask",
|
||||
"gateway": "Gateway address",
|
||||
"dns_servers": "DNS servers",
|
||||
"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