From bb84deffa2b4a26280ff5eda2c999c74c2b7ed01 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 3 Oct 2024 15:46:12 +0200 Subject: [PATCH] Check host network connectivity Check host network connectivity and offer DNS alternatives in case host internet is not available. This allows users with broken DNS setup to configure the host network with a working DNS. Requires https://github.com/home-assistant/supervisor/pull/5321. --- rootfs/usr/share/www/index.html | 28 ++++++- rootfs/usr/share/www/static/scripts.js | 100 ++++++++++++++++++++++++- rootfs/usr/share/www/static/styles.css | 34 ++++++++- 3 files changed, 157 insertions(+), 5 deletions(-) diff --git a/rootfs/usr/share/www/index.html b/rootfs/usr/share/www/index.html index 9aca701..16d8253 100644 --- a/rootfs/usr/share/www/index.html +++ b/rootfs/usr/share/www/index.html @@ -26,7 +26,7 @@

This may take 20 minutes or more

-
+

Error installing Home Assistant

+
+

Networking issue detected

+ +
+ + +
+

     
   
diff --git a/rootfs/usr/share/www/static/scripts.js b/rootfs/usr/share/www/static/scripts.js
index de00e94..4424c0f 100644
--- a/rootfs/usr/share/www/static/scripts.js
+++ b/rootfs/usr/share/www/static/scripts.js
@@ -78,7 +78,7 @@ function fetchLogs() {
       res.text().then(function (text) {
         var logElement = document.getElementById("log");
         if (errorCheck.test(text)) {
-          document.body.classList.add("error");
+          document.body.classList.add("supervisor-error");
           document.getElementById("show_logs").innerText = "Download raw logs";
           logElement.showFull = true;
         }
@@ -109,8 +109,40 @@ function scheduleFetchLogs() {
   scheduleTimeout = setTimeout(fetchLogs, 5000);
 }
 
+function fetchNetworkInfo() {
+  fetch("/supervisor/network/info").then(function (res) {
+    if (!res.ok)
+      return;
+
+    res.json().then(function (data) {
+      if (!data.data.host_internet) {
+        document.body.classList.add("network-issue");
+      }
+
+      if (document.body.classList.contains("network-issue")) {
+        const primaryInterface = data.data.interfaces.find(intf => intf.primary);
+        var dnsElement = document.getElementById("current_dns");
+        if (!primaryInterface) {
+          dnsElement.innerText = "(no primary interface)";
+        } else {
+          dnsElement.innerText = [...(primaryInterface.ipv4?.nameservers || []), ...(primaryInterface.ipv6?.nameservers || [])].join(', ');
+        }
+      }
+
+    });
+  }, scheduleFetchNetworkInfo());
+}
+
+var scheduleNetworkTimeout;
+
+function scheduleFetchNetworkInfo() {
+  clearTimeout(scheduleNetworkTimeout);
+  scheduleNetworkTimeout = setTimeout(fetchNetworkInfo, 5000);
+}
+
 scheduleTry();
 fetchLogs();
+fetchNetworkInfo();
 
 document.getElementById("show_logs").addEventListener("click", toggleLogs);
 function toggleLogs(event) {
@@ -136,6 +168,72 @@ function toggleLogs(event) {
   }
 }
 
+document.getElementById("try_cloudflare_dns").addEventListener("click", function() {
+  setDns(["1.1.1.1", "1.0.0.1"], ["2606:4700:4700::1111", "2606:4700:4700::1001"]);
+});
+
+document.getElementById("try_google_dns").addEventListener("click", function() {
+  setDns(["8.8.8.8", "8.8.4.4"], ["2001:4860:4860::8888", "2001:4860:4860::8844"]);
+});
+
+function setDns(ipv4nameservers, ipv6nameservers) {
+  // Step 1: Fetch the primary network interface from the /network/info endpoint
+  fetch("/supervisor/network/info", {
+      method: 'GET',
+      headers: {
+          'Content-Type': 'application/json',
+      }
+  })
+  .then(response => {
+      if (!response.ok) {
+          throw new Error('Failed to fetch network info');
+      }
+      return response.json();
+  })
+  .then(data => {
+      // Step 2: Find the primary interface
+      const primaryInterface = data.data.interfaces.find(intf => intf.primary && intf.enabled);
+      if (!primaryInterface) {
+          throw new Error('No primary interface found');
+      }
+
+      // Step 3: Update the DNS settings for the primary interface
+      const payload = {
+          ipv4: {
+              method: "auto",
+              nameservers: ipv4nameservers
+          },
+          ipv6: {
+              method: "auto",
+              nameservers: ipv6nameservers
+          }
+      };
+
+      return fetch(`/supervisor/network/interface/${primaryInterface.interface}/update`, {
+          method: 'POST',
+          headers: {
+              'Content-Type': 'application/json',
+          },
+          body: JSON.stringify(payload)
+      });
+  })
+  .then(response => {
+      if (!response.ok) {
+          throw new Error('Failed to update the interface');
+      }
+      fetchNetworkInfo();
+      return response.json();
+  })
+  .then(data => {
+      console.log('Success:', data);
+      // Optionally handle the success case, e.g., updating the UI or showing a message
+  })
+  .catch((error) => {
+      console.error('Error:', error);
+      // Optionally handle the error case, e.g., showing an error message
+  });
+}
+
 var dialogs = document.querySelectorAll('dialog');
 dialogs.forEach(dialog => {
   dialogPolyfill.registerDialog(dialog);
diff --git a/rootfs/usr/share/www/static/styles.css b/rootfs/usr/share/www/static/styles.css
index 9de8f03..6ec1ebf 100644
--- a/rootfs/usr/share/www/static/styles.css
+++ b/rootfs/usr/share/www/static/styles.css
@@ -30,15 +30,15 @@ body {
   white-space: nowrap;
 }
 
-.error .state-normal {
+.supervisor-error .state-normal {
   display: none;
 }
 
-.state-error {
+#state-error {
   display: none;
 }
 
-.error .state-error {
+.supervisor-error #state-error {
   display: block;
 }
 
@@ -60,6 +60,9 @@ body {
   pointer-events: none;
   content: "";
   border-radius: 4px;
+}
+
+.error .alert::after {
   background-color: #db4437;
 }
 
@@ -76,6 +79,31 @@ body {
   margin-right: 0;
 }
 
+#state-network-issue {
+  display: none;
+}
+
+.network-issue #state-network-issue {
+  display: block;
+}
+
+.network-issue .state-normal {
+  display: none;
+}
+
+.warning .alert-icon {
+  fill: #ffa600;
+}
+
+.warning .alert::after {
+  background-color: #ffa600;
+}
+
+.warning .actions {
+  display: flex;
+  margin-bottom: 16px;
+}
+
 .header {
   text-align: center;
   margin-top: 32px;