Repository: home-assistant/frontend
Pull request #25130: Use numeric prefix for IPv6 mask
Author: login: MindFreeze
Labels: name: cla-signed
Created: 2025-04-22T08:09:07Z | Merged: 2025-04-24T14:45:38Z

<!--
  You are amazing! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->

## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue or discussion
  in the additional information section.
-->
The current IPv6 configuration uses hexadecimal netmasks for IPv6 addresses. This is really not common in IPv6 world (or rather, I've never seen this in any OS network configuration dialog), also very error prone (since it is hard to keep count of so many f's) and therefor not user friendly.

For IPv6 configuration, the netmask should be represented as a simple numeric prefix (e.g. 64).

@agners 

https://github.com/home-assistant/frontend/pull/22320#issuecomment-2427480017

## Type of change
<!--
  What type of change does your PR introduce to the Home Assistant frontend?
  NOTE: Please, check only 1! box!
  If your PR requires multiple boxes to be checked, you'll most likely need to
  split it into multiple PRs. This makes things easier and faster to code review.
-->

- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] New feature (thank you!)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Example configuration
<!--
  Supplying a configuration snippet, makes it easier for a maintainer to test
  your PR.
-->

```yaml

```

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #
- This PR is related to issue or discussion:
- Link to documentation pull request:

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [x] The code change is tested and works locally.
- [x] There is no commented out code in this PR.
- [x] Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]

<!--
  Thank you for contributing <3
-->

[docs-repository]: https://github.com/home-assistant/home-assistant.io



Diff:
diff --git a/src/data/hassio/network.ts b/src/data/hassio/network.ts
index 40500360afea..1d036a04ee0e 100644
--- a/src/data/hassio/network.ts
+++ b/src/data/hassio/network.ts
@@ -120,7 +120,7 @@ export const parseAddress = (address: string) => {
   const [ip, cidr] = address.split("/");
   const isIPv6 = ip.includes(":");
   const mask = cidr ? cidrToNetmask(cidr, isIPv6) : null;
-  return { ip, mask };
+  return { ip, mask, prefix: cidr };
 };
 
 export const formatAddress = (ip: string, mask: string) =>
diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts
index 93d03c7db954..27f934edaef2 100644
--- a/src/panels/config/network/supervisor-network.ts
+++ b/src/panels/config/network/supervisor-network.ts
@@ -380,7 +380,7 @@ export class HassioNetwork extends LitElement {
           ? html`
               ${this._interface![version].address.map(
                 (address: string, index: number) => {
-                  const { ip, mask } = parseAddress(address);
+                  const { ip, mask, prefix } = parseAddress(address);
                   return html`
                     <div class="address-row">
                       <ha-textfield
@@ -395,18 +395,35 @@ export class HassioNetwork extends LitElement {
                         .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>
+                      ${version === "ipv6"
+                        ? html`
+                            <ha-textfield
+                              id="prefix"
+                              .label=${this.hass.localize(
+                                "ui.panel.config.network.supervisor.prefix"
+                              )}
+                              .version=${version}
+                              .value=${prefix || ""}
+                              .index=${index}
+                              @change=${this._handleInputValueChanged}
+                              .disabled=${disableInputs}
+                            >
+                            </ha-textfield>
+                          `
+                        : html`
+                            <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`
@@ -654,25 +671,30 @@ export class HassioNetwork extends LitElement {
     if (id === "address") {
       const index = (ev.target as any).index as number;
       const { mask: oldMask } = parseAddress(
-        this._interface![version]!.address![index]
+        this._interface[version].address![index]
       );
       const { mask } = parseAddress(value);
-      this._interface[version]!.address![index] = formatAddress(
+      this._interface[version].address![index] = formatAddress(
         value,
         mask || oldMask || ""
       );
       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);
+      const { ip } = parseAddress(this._interface[version].address![index]);
+      this._interface[version].address![index] = formatAddress(ip, value);
+      this.requestUpdate("_interface");
+    } else if (id === "prefix") {
+      const index = (ev.target as any).index as number;
+      const { ip } = parseAddress(this._interface[version].address![index]);
+      this._interface[version].address![index] = `${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._interface[version].nameservers![index] = value;
       this.requestUpdate("_interface");
     } else {
-      this._interface[version]![id] = value;
+      this._interface[version][id] = value;
     }
   }
 
@@ -790,6 +812,10 @@ export class HassioNetwork extends LitElement {
         .address-row ha-textfield {
           flex: 1;
         }
+        .address-row #prefix {
+          flex: none;
+          width: 95px;
+        }
         .address-row ha-icon-button {
           --mdc-icon-button-size: 36px;
           margin-top: 16px;
diff --git a/src/translations/en.json b/src/translations/en.json
index 2e4330ad0c6b..38b7b408b5a9 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -6319,6 +6319,7 @@
             "disabled": "Disabled",
             "ip": "IP address",
             "netmask": "Netmask",
+            "prefix": "Subnet prefix",
             "add_address": "Add address",
             "gateway": "Gateway address",
             "dns_server": "DNS Server",


Reviews:
user: login: agners
body: Looks quite good

![image](https://github.com/user-attachments/assets/43278bab-6688-43dc-bb75-cb58ad16dc86)

Minor tweak: Can you make the prefix field smaller, so that even full IPv6 addresses are displayed completely?



state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T09:43:17Z
user: login: agners
body: LGTM, thanks!
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-24T12:19:29Z
user: login: wendevlin
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-24T12:31:08Z
user: login: MindFreeze
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T14:26:09Z
user: login: wendevlin
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T14:44:10Z
user: login: wendevlin
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T14:44:26Z

Review comments:
diff_hunk: @@ -667,6 +684,11 @@ export class HassioNetwork extends LitElement {
       const { ip } = parseAddress(this._interface![version]!.address![index]);
       this._interface[version]!.address![index] = formatAddress(ip, value);
       this.requestUpdate("_interface");
+    } else if (id === "prefix") {
+      const index = (ev.target as any).index as number;
+      const { ip } = parseAddress(this._interface![version]!.address![index]);
+      this._interface[version]!.address![index] = `${ip}/${value}`;
+      this.requestUpdate("_interface");
path: src/panels/config/network/supervisor-network.ts
user: login: wendevlin
body: This is duplicate code. 

You can check if its `prefix` or `netmask` and then just make an inline if for the interface address set.
created_at: 2025-04-24T12:30:53Z
author_association: MEMBER
diff_hunk: @@ -667,6 +684,11 @@ export class HassioNetwork extends LitElement {
       const { ip } = parseAddress(this._interface![version]!.address![index]);
       this._interface[version]!.address![index] = formatAddress(ip, value);
       this.requestUpdate("_interface");
+    } else if (id === "prefix") {
+      const index = (ev.target as any).index as number;
+      const { ip } = parseAddress(this._interface![version]!.address![index]);
+      this._interface[version]!.address![index] = `${ip}/${value}`;
+      this.requestUpdate("_interface");
path: src/panels/config/network/supervisor-network.ts
user: login: MindFreeze
body: Then the check will be duplicated. At least this makes it clear what happens for each field
created_at: 2025-04-24T14:26:09Z
author_association: MEMBER
diff_hunk: @@ -667,6 +684,11 @@ export class HassioNetwork extends LitElement {
       const { ip } = parseAddress(this._interface![version]!.address![index]);
       this._interface[version]!.address![index] = formatAddress(ip, value);
       this.requestUpdate("_interface");
+    } else if (id === "prefix") {
+      const index = (ev.target as any).index as number;
+      const { ip } = parseAddress(this._interface![version]!.address![index]);
+      this._interface[version]!.address![index] = `${ip}/${value}`;
+      this.requestUpdate("_interface");
path: src/panels/config/network/supervisor-network.ts
user: login: wendevlin
body: okay

created_at: 2025-04-24T14:44:10Z
author_association: MEMBER

PR comments:
user: login: MindFreeze
created_at: 2025-04-23T05:12:24Z
body: ![image](https://github.com/user-attachments/assets/189a8bf9-cfd2-40c5-8730-e62284c7f547)
author_association: MEMBER
user: login: home-assistant[bot]
created_at: 2025-04-24T12:31:10Z
body: <!-- ReviewDrafterComment -->
Please take a look at the requested changes, and use the **Ready for review** button when you are done, thanks :+1:

[_Learn more about our pull request process._](https://developers.home-assistant.io/docs/review-process#prs-are-being-drafted-when-changes-are-needed)

author_association: NONE