Explorar el Código

Re-create service ping, docker status changes

See #388
Michael Shamoon hace 2 años
padre
commit
1c456b70c0

+ 7 - 1
public/locales/en/common.json

@@ -52,7 +52,13 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Offline"
+        "offline": "Offline",
+        "error": "Error",
+        "unknown": "Unknown"
+    },
+    "ping": {
+        "error": "Error",
+        "ping": "Ping"
     },
     "emby": {
         "playing": "Playing",

+ 21 - 11
src/components/services/item.jsx

@@ -3,6 +3,7 @@ import { useContext, useState } from "react";
 
 import Status from "./status";
 import Widget from "./widget";
+import Ping from "./ping";
 
 import Docker from "widgets/docker/component";
 import { SettingsContext } from "utils/contexts/settings";
@@ -30,7 +31,7 @@ export default function Item({ service }) {
       <div
         className={`${
           hasLink ? "cursor-pointer " : " "
-        }transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10`}
+        }transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative`}
       >
         <div className="flex select-none">
           {service.icon &&
@@ -70,16 +71,25 @@ export default function Item({ service }) {
             </div>
           )}
 
-          {service.container && (
-            <button
-              type="button"
-              onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
-              className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
-            >
-              <Status service={service} />
-              <span className="sr-only">View container stats</span>
-            </button>
-          )}
+          <div className="absolute top-0 right-0 w-1/2 flex flex-row justify-end gap-2 mr-2">
+              {service.ping && (
+                <div className="flex-shrink-0 flex items-center justify-center cursor-pointer">
+                  <Ping service={service} />
+                  <span className="sr-only">Ping status</span>
+                </div>
+              )}
+
+              {service.container && (
+                <button
+                  type="button"
+                  onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
+                  className="flex-shrink-0 flex items-center justify-center cursor-pointer"
+                >
+                  <Status service={service} />
+                  <span className="sr-only">View container stats</span>
+                </button>
+              )}
+          </div>
         </div>
 
         {service.container && service.server && (

+ 44 - 0
src/components/services/ping.jsx

@@ -0,0 +1,44 @@
+import { useTranslation } from "react-i18next";
+import useSWR from "swr";
+
+export default function Ping({ service }) {
+  const { t } = useTranslation();
+  const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ping: service.ping}).toString()}`, {
+    refreshInterval: 30000
+  });
+
+  if (error) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
+        <div className="text-[8px] font-bold text-rose-500 uppercase">{t("ping.error")}</div>
+      </div>
+    );
+  }
+  
+  if (!data) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
+        <div className="text-[8px] font-bold text-black/20 dark:text-white/40 uppercase">{t("ping.ping")}</div>
+      </div>
+    );
+  }
+
+  const statusText = `${service.ping}: HTTP status ${data.status}`;
+  
+  if (data && data.status !== 200) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={statusText}>
+        <div className="text-[8px] font-bold text-rose-500/80">{data.status}</div>
+      </div>
+    );
+  }
+  
+  if (data && data.status === 200) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={statusText}>
+        <div className="text-[8px] font-bold text-emerald-500/80">{t("common.ms", { value: data.latency, style: "unit", unit: "millisecond", unitDisplay: "narrow", maximumFractionDigits: 0 })}</div>
+      </div>
+    );
+  }
+
+}

+ 22 - 5
src/components/services/status.jsx

@@ -1,19 +1,36 @@
+import { useTranslation } from "react-i18next";
 import useSWR from "swr";
 
 export default function Status({ service }) {
+  const { t } = useTranslation();
+
   const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
 
   if (error) {
-    return <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />;
+    <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
+      <div className="text-[8px] font-bold text-rose-500/80 uppercase">{t("docker.error")}</div>
+    </div>
   }
 
   if (data && data.status === "running") {
-    return <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />;
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
+        <div className="text-[8px] font-bold text-emerald-500/80 uppercase">{data.status}</div>
+      </div>
+    );
   }
 
-  if (data && data.status === "not found") {
-    return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />;
+  if (data && (data.status === "not found" || data.status === "exited")) {
+    return (
+      <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
+        <div className="text-[8px] font-bold text-orange-400/50 dark:text-orange-400/80 uppercase">{data.status}</div>
+      </div>
+    );
   }
 
-  return <div className="w-3 h-3 bg-black/20 dark:bg-white/40 rounded-full" />;
+  return (
+    <div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
+      <div className="text-[8px] font-bold text-black/20 dark:text-white/40 uppercase">{t("docker.unknown")}</div>
+    </div>
+  );
 }

+ 28 - 0
src/pages/api/ping.js

@@ -0,0 +1,28 @@
+import { performance } from "perf_hooks";
+
+import createLogger from "utils/logger";
+import { httpProxy } from "utils/proxy/http";
+
+const logger = createLogger("ping");
+
+export default async function handler(req, res) {
+    const { ping: pingURL } = req.query;
+
+    if (!pingURL) {
+        logger.debug("No ping URL specified");
+        return res.status(400).send({
+        error: "No ping URL given",
+        });
+    }
+    
+    const startTime = performance.now();
+    const [status] = await httpProxy(pingURL, {
+      method: "HEAD"
+    });
+    const endTime = performance.now();
+
+    return res.status(200).json({
+      status,
+      latency: endTime - startTime
+    });
+}

+ 1 - 1
src/utils/proxy/http.js

@@ -96,7 +96,7 @@ export async function httpProxy(url, params = {}) {
     return [status, contentType, data, responseHeaders];
   }
   catch (err) {
-    logger.error("Error calling %s//%s%s...", url.protocol, url.hostname, url.pathname);
+    logger.error("Error calling %s//%s%s...", constructedUrl.protocol, constructedUrl.hostname, constructedUrl.pathname);
     logger.error(err);
     return [500, "application/json", { error: "Unexpected error" }, null];
   }