mirror of
				https://github.com/karl0ss/homepage.git
				synced 2025-11-04 00:10:57 +00:00 
			
		
		
		
	Merge pull request #505 from benphelps/ping-and-redesign-status
Feature: Service ping, redesign docker status
This commit is contained in:
		
						commit
						354f819041
					
				@ -56,7 +56,13 @@
 | 
				
			|||||||
        "tx": "TX",
 | 
					        "tx": "TX",
 | 
				
			||||||
        "mem": "MEM",
 | 
					        "mem": "MEM",
 | 
				
			||||||
        "cpu": "CPU",
 | 
					        "cpu": "CPU",
 | 
				
			||||||
        "offline": "Offline"
 | 
					        "offline": "Offline",
 | 
				
			||||||
 | 
					        "error": "Error",
 | 
				
			||||||
 | 
					        "unknown": "Unknown"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "ping": {
 | 
				
			||||||
 | 
					        "error": "Error",
 | 
				
			||||||
 | 
					        "ping": "Ping"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "emby": {
 | 
					    "emby": {
 | 
				
			||||||
        "playing": "Playing",
 | 
					        "playing": "Playing",
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { useContext, useState } from "react";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import Status from "./status";
 | 
					import Status from "./status";
 | 
				
			||||||
import Widget from "./widget";
 | 
					import Widget from "./widget";
 | 
				
			||||||
 | 
					import Ping from "./ping";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Docker from "widgets/docker/component";
 | 
					import Docker from "widgets/docker/component";
 | 
				
			||||||
import { SettingsContext } from "utils/contexts/settings";
 | 
					import { SettingsContext } from "utils/contexts/settings";
 | 
				
			||||||
@ -30,7 +31,7 @@ export default function Item({ service }) {
 | 
				
			|||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={`${
 | 
					        className={`${
 | 
				
			||||||
          hasLink ? "cursor-pointer " : " "
 | 
					          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">
 | 
					        <div className="flex select-none">
 | 
				
			||||||
          {service.icon &&
 | 
					          {service.icon &&
 | 
				
			||||||
@ -70,16 +71,25 @@ export default function Item({ service }) {
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {service.container && (
 | 
					          <div className="absolute top-0 right-0 w-1/2 flex flex-row justify-end gap-2 mr-2">
 | 
				
			||||||
            <button
 | 
					              {service.ping && (
 | 
				
			||||||
              type="button"
 | 
					                <div className="flex-shrink-0 flex items-center justify-center cursor-pointer">
 | 
				
			||||||
              onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
 | 
					                  <Ping service={service} />
 | 
				
			||||||
              className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
 | 
					                  <span className="sr-only">Ping status</span>
 | 
				
			||||||
            >
 | 
					                </div>
 | 
				
			||||||
              <Status service={service} />
 | 
					              )}
 | 
				
			||||||
              <span className="sr-only">View container stats</span>
 | 
					
 | 
				
			||||||
            </button>
 | 
					              {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>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {service.container && service.server && (
 | 
					        {service.container && service.server && (
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										44
									
								
								src/components/services/ping.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/components/services/ping.jsx
									
									
									
									
									
										Normal file
									
								
							@ -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>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,19 +1,36 @@
 | 
				
			|||||||
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import useSWR from "swr";
 | 
					import useSWR from "swr";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function Status({ service }) {
 | 
					export default function Status({ service }) {
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
 | 
					  const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (error) {
 | 
					  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") {
 | 
					  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") {
 | 
					  if (data && (data.status === "not found" || data.status === "exited")) {
 | 
				
			||||||
    return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />;
 | 
					    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
									
								
								src/pages/api/ping.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/pages/api/ping.js
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -96,7 +96,7 @@ export async function httpProxy(url, params = {}) {
 | 
				
			|||||||
    return [status, contentType, data, responseHeaders];
 | 
					    return [status, contentType, data, responseHeaders];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  catch (err) {
 | 
					  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);
 | 
					    logger.error(err);
 | 
				
			||||||
    return [500, "application/json", { error: {message: err?.message ?? "Unknown error", url, rawError: err} }, null];
 | 
					    return [500, "application/json", { error: {message: err?.message ?? "Unknown error", url, rawError: err} }, null];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user