From 67abf8b7b9fdd519a764963d26dbc660489b7e60 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 7 Oct 2024 17:48:09 +0200 Subject: [PATCH] Check host network connectivity (#128) * 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. * Apply suggestions from code review Co-authored-by: Bram Kragten * Remove network issue warning once resolved * Make const var again All consts need to be assigned on creation. This is not possible in this case. * Improve wording * Replace try with use * Improve network issue wording * Rename function, call schedule outside of then() The idea is to call schedule() always, no matter if there is an error or not. So call it outside of the then() block. * Use let --------- Co-authored-by: Bram Kragten --- rootfs/usr/share/www/index.html | 31 ++++++- rootfs/usr/share/www/static/scripts.js | 110 ++++++++++++++++++++++++- rootfs/usr/share/www/static/styles.css | 34 +++++++- 3 files changed, 168 insertions(+), 7 deletions(-) diff --git a/rootfs/usr/share/www/index.html b/rootfs/usr/share/www/index.html index 9aca701..85fc2a8 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 dc08a37..74c7145 100644
--- a/rootfs/usr/share/www/static/scripts.js
+++ b/rootfs/usr/share/www/static/scripts.js
@@ -81,7 +81,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;
         }
@@ -98,22 +98,60 @@ function fetchLogs() {
         }
       });
     }
-  }, scheduleFetchLogs());
+  });
+  scheduleFetchLogs();
 }
 
 function scheduleTry() {
   setTimeout(testAvailable, 5000);
 }
 
-var scheduleTimeout;
+let scheduleTimeout;
 
 function scheduleFetchLogs() {
   clearTimeout(scheduleTimeout);
   scheduleTimeout = setTimeout(fetchLogs, 5000);
 }
 
+function fetchSupervisorInfo() {
+  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");
+      }
+      else
+      {
+        document.body.classList.remove("network-issue");
+      }
+
+      if (document.body.classList.contains("network-issue")) {
+        const primaryInterface = data.data.interfaces.find(intf => intf.primary);
+        const dnsElement = document.getElementById("current_dns");
+        if (!primaryInterface) {
+          dnsElement.innerText = "(no primary interface)";
+        } else {
+          dnsElement.innerText = [...(primaryInterface.ipv4?.nameservers || []), ...(primaryInterface.ipv6?.nameservers || [])].join(', ');
+        }
+      }
+
+    });
+  });
+  scheduleFetchSupervisorInfo();
+}
+
+let scheduleSupervisorTimeout;
+
+function scheduleFetchSupervisorInfo() {
+  clearTimeout(scheduleSupervisorTimeout);
+  scheduleSupervisorTimeout = setTimeout(fetchSupervisorInfo, 5000);
+}
+
 scheduleTry();
 fetchLogs();
+fetchSupervisorInfo();
 
 document.getElementById("show_logs").addEventListener("click", toggleLogs);
 function toggleLogs(event) {
@@ -139,6 +177,72 @@ function toggleLogs(event) {
   }
 }
 
+document.getElementById("use_cloudflare_dns").addEventListener("click", function() {
+  setDns(["1.1.1.1", "1.0.0.1"], ["2606:4700:4700::1111", "2606:4700:4700::1001"]);
+});
+
+document.getElementById("use_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');
+      }
+      fetchSupervisorInfo();
+      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 6e99914..e1306c0 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;