From 3303008ec38449e8f5eed300e619cfdadbf09d95 Mon Sep 17 00:00:00 2001 From: Neshura Date: Sun, 11 Dec 2022 16:30:11 +0100 Subject: [PATCH] incremental Data Fetching using useSWR not quite like I want but should be good enough --- pages/services.tsx | 214 +++++++++++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 87 deletions(-) diff --git a/pages/services.tsx b/pages/services.tsx index b906318..383272e 100644 --- a/pages/services.tsx +++ b/pages/services.tsx @@ -1,32 +1,28 @@ import Head from 'next/head' import Link from 'next/link' import styles from '/styles/Home.module.css' -import { Service, CustomLink, EntryList, ServiceStatus, ServiceType, ServiceLocation } from '../interfaces/LinkTypes' +import { Service, ServiceStatus, ServiceType, ServiceLocation } from '../interfaces/LinkTypes'; import Dockerode from 'dockerode'; -import { ReactElement, useEffect, useState } from 'react' -import useSWR, { KeyedMutator } from 'swr'; +import { ReactElement } from 'react' +import useSWR from 'swr'; const fetcher = (url: string) => fetch(url).then((res) => res.json()) //function Services(props: EntryList) { -const Services = () => { - const { serviceList, isLoading, isError } = useServices(); +function Services() { + const { initialData, fullData, loadingInitial, loadingFull, error } = useServices(); + let content: ReactElement = <>; - // TODO: look into asyncing this as well - useStatus(serviceList); - - if (isError) { - content =
Error Loading data
+ if (error) { content =
Error loading data
} + else if (loadingInitial) { + content =
Loading
} - else if (isLoading) { - content =
Loading..
- } - else if (serviceList) { + else if (loadingFull) { content =
- {serviceList.map((item: Service) => ( + {initialData?.map((item: Service) => (

{item.name}

@@ -38,6 +34,25 @@ const Services = () => { ))}
} + else if (fullData) { + content = +
+ {fullData.map((item: Service) => ( + + +

{item.name}

+
{item.status}
+

{item.desc}

+

{item.warn}

+
+ + ))} +
+ } + else { + content =
Error loading data
+ } + return ( <> @@ -60,96 +75,121 @@ const Services = () => { ) } -function useStatus(serviceList: Service[] | undefined) { - const { data, error } = useSWR('/api/containers', fetcher); +async function getStatus(entry: Service, containers: Dockerode.ContainerInfo[]) { + // Currently the only location supporting different fetching depending on type is brr7-4800u + // Others to follow but low prio as this is currently the only location used - if (data && serviceList) { - serviceList.forEach((service: Service) => { - getStatus(data, service); - }) - } - else if (error) { - console.log(error); - } -} - -function useServices(): { serviceList: Service[] | undefined, isLoading: boolean, isError: boolean } { - const { data, error } = useSWR('/api/services', fetcher); - - return { - serviceList: data, - isLoading: !error && !data, - isError: error - }; -} - -function getStatus(containers: Dockerode.ContainerInfo[], entry: Service) { - // for now only the BRR7-4800U can be used with Docker, needs changing once more Servers are used - // TODO: support multiple locations for docker + // Location BRR7-4800U if (entry.location === ServiceLocation.brr7_4800u) { - // app in this context means any non-docker page + // Type APP if (entry.type === ServiceType.app) { - fetch(entry.href) - .then((data: Response) => { - if (data.ok) { - if (data.status == 200 || data.status == 301 || data.status == 302) { - entry.status = ServiceStatus.online; - } - else { - entry.status = ServiceStatus.offline; + await fetch(entry.href) + .then((response) => { + if (response.ok) { + switch(response.status) { + case 200: + case 301: + case 302: + entry.status = ServiceStatus.online; + break; + default: + entry.status = ServiceStatus.offline; } } else { entry.status = ServiceStatus.offline; } }) - .catch(error => { - console.log(error); + .catch((error) => { + console.error("Error pinging Website: ", error); entry.status = ServiceStatus.error; - }); + }) } - else if (entry.type === "docker") { - entry.status = ServiceStatus.offline; - // Loop over every found container and compare to the entry provided - containers.forEach((container: Dockerode.ContainerInfo) => { - console.log(container) - if (container.Names.includes("/" + entry.docker_container_name) || container.Names.includes(entry.docker_container_name)) { - if (container.State === "running") { - entry.status = ServiceStatus.online; + // Type Docker + else if (entry.type === ServiceType.docker) { + containers.forEach((container) => { + // Docker API returns container names with / prepended, not sure whether this always happens so both cases are checked + if (container.Names.includes( entry.docker_container_name || "/" + entry.docker_container_name)) { + // so far only "running" is properly implemented, mroe cases to follow as needed + switch (container.State) { + case "running": + entry.status = ServiceStatus.online; + break; + default: + console.log("Container Status " + container.State + " has no case implemented"); + entry.status = ServiceStatus.offline; + } + } + // If container name is not missing the container is set to offline + else if (entry.docker_container_name !== null) { + console.warn("Container for " + entry.name + " could not be found"); + entry.status = ServiceStatus.offline; + } + else { + console.error("Container Name not specified"); + entry.status = ServiceStatus.error; + } + }) + } + // If no Type matches + else { + console.warn("Service Type for Service " + entry.name + " not specified or invalid"); + entry.status = ServiceStatus.error; + } + } + // Location Other + // TODO: implement docker type for other locations + else if (entry.location === ServiceLocation.other) { + // Currently uses the same handling as app type for the other location + await fetch(entry.href) + .then((response) => { + if (response.ok) { + switch(response.status) { + case 200: + case 301: + case 302: + entry.status = ServiceStatus.online; + break; + default: + entry.status = ServiceStatus.offline; + } } else { entry.status = ServiceStatus.offline; } - } - if (entry.docker_container_name == null) { - console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name); + }) + .catch((error) => { + console.error("Error pinging Website: ", error); entry.status = ServiceStatus.error; - } - }); - } - else { entry.status = ServiceStatus.error } + }) } - // for non-local locations pinging can be used to see if they're up - else if (entry.location === ServiceLocation.other) { - fetch(entry.href) - .then((data) => { - if (data.ok) { - if (data.status == 200 || data.status == 301 || data.status == 302) { - return (entry.status = ServiceStatus.online); - } - else (entry.status = ServiceStatus.offline); - } - else { - entry.status = ServiceStatus.offline; - } - }) - .catch((error) => { - console.log(error); - entry.status = ServiceStatus.error; - }); + // If no Location matches + else { + console.warn("Service Location for Service " + entry.name + " not specified"); + entry.status = ServiceStatus.error; } - else { entry.status = ServiceStatus.error } - return; + return entry; +} + +const fetchFullDataArray = (containerData: Dockerode.ContainerInfo[], dataSet: Service[]) => { + const fetchStatus = (entry: Service) => getStatus(entry, containerData); + return Promise.all(dataSet.map(fetchStatus)); +} + +function useServices() { + const { data: containerData, error: containerError } = useSWR('/api/containers', fetcher); + const { data: initialData, error: initialError } = useSWR('/api/services', fetcher); + const loadingInitial = !initialData && !initialError + const { data: fullData, error: fullError } = useSWR((initialData && containerData) ? [containerData, initialData] : null, fetchFullDataArray) + const loadingFull = !fullData && !fullError + + return { + initialData, + fullData, + loadingInitial, + loadingFull, + error: initialError || fullError || containerError, + }; } export default Services \ No newline at end of file