incremental Data Fetching using useSWR

not quite like I want but should be good enough
This commit is contained in:
Neshura 2022-12-11 16:30:11 +01:00
parent fff04d019a
commit 3303008ec3
No known key found for this signature in database
GPG key ID: ACDF5B6EBECF6B0A

View file

@ -1,32 +1,28 @@
import Head from 'next/head' import Head from 'next/head'
import Link from 'next/link' import Link from 'next/link'
import styles from '/styles/Home.module.css' 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 Dockerode from 'dockerode';
import { ReactElement, useEffect, useState } from 'react' import { ReactElement } from 'react'
import useSWR, { KeyedMutator } from 'swr'; import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then((res) => res.json()) const fetcher = (url: string) => fetch(url).then((res) => res.json())
//function Services(props: EntryList) { //function Services(props: EntryList) {
const Services = () => { function Services() {
const { serviceList, isLoading, isError } = useServices(); const { initialData, fullData, loadingInitial, loadingFull, error } = useServices();
let content: ReactElement = <></>; let content: ReactElement = <></>;
// TODO: look into asyncing this as well if (error) { content = <div>Error loading data</div> }
useStatus(serviceList); else if (loadingInitial) {
content = <div>Loading</div>
if (isError) {
content = <div className={styles.grid}>Error Loading data</div>
} }
else if (isLoading) { else if (loadingFull) {
content = <div className={styles.grid}>Loading..</div>
}
else if (serviceList) {
content = content =
<div className={styles.grid}> <div className={styles.grid}>
{serviceList.map((item: Service) => ( {initialData?.map((item: Service) => (
<Link key={item.name} href={item.href}> <Link key={item.name} href={item.href}>
<a className={styles.contentcard}> <a className={styles.contentcard}>
<div className={styles.contenttitle}><h2>{item.name}</h2></div> <div className={styles.contenttitle}><h2>{item.name}</h2></div>
@ -38,6 +34,25 @@ const Services = () => {
))} ))}
</div> </div>
} }
else if (fullData) {
content =
<div className={styles.grid}>
{fullData.map((item: Service) => (
<Link key={item.name} href={item.href}>
<a className={styles.contentcard}>
<div className={styles.contenttitle}><h2>{item.name}</h2></div>
<div className={item.status === ServiceStatus.online ? styles.contentonline : styles.contentoffline}>{item.status}</div>
<div><p>{item.desc}</p></div>
<div className={styles.cardwarn}><p>{item.warn}</p></div>
</a>
</Link>
))}
</div>
}
else {
content = <div>Error loading data</div>
}
return ( return (
<> <>
<Head> <Head>
@ -60,96 +75,121 @@ const Services = () => {
) )
} }
function useStatus(serviceList: Service[] | undefined) { async function getStatus(entry: Service, containers: Dockerode.ContainerInfo[]) {
const { data, error } = useSWR('/api/containers', fetcher); // 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) { // Location BRR7-4800U
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
if (entry.location === ServiceLocation.brr7_4800u) { if (entry.location === ServiceLocation.brr7_4800u) {
// app in this context means any non-docker page // Type APP
if (entry.type === ServiceType.app) { if (entry.type === ServiceType.app) {
fetch(entry.href) await fetch(entry.href)
.then((data: Response) => { .then((response) => {
if (data.ok) { if (response.ok) {
if (data.status == 200 || data.status == 301 || data.status == 302) { switch(response.status) {
entry.status = ServiceStatus.online; case 200:
} case 301:
else { case 302:
entry.status = ServiceStatus.offline; entry.status = ServiceStatus.online;
break;
default:
entry.status = ServiceStatus.offline;
} }
} }
else { else {
entry.status = ServiceStatus.offline; entry.status = ServiceStatus.offline;
} }
}) })
.catch(error => { .catch((error) => {
console.log(error); console.error("Error pinging Website: ", error);
entry.status = ServiceStatus.error; entry.status = ServiceStatus.error;
}); })
} }
else if (entry.type === "docker") { // Type Docker
entry.status = ServiceStatus.offline; else if (entry.type === ServiceType.docker) {
// Loop over every found container and compare to the entry provided containers.forEach((container) => {
containers.forEach((container: Dockerode.ContainerInfo) => { // Docker API returns container names with / prepended, not sure whether this always happens so both cases are checked
console.log(container) if (container.Names.includes( entry.docker_container_name || "/" + entry.docker_container_name)) {
if (container.Names.includes("/" + entry.docker_container_name) || container.Names.includes(entry.docker_container_name)) { // so far only "running" is properly implemented, mroe cases to follow as needed
if (container.State === "running") { switch (container.State) {
entry.status = ServiceStatus.online; 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 { else {
entry.status = ServiceStatus.offline; entry.status = ServiceStatus.offline;
} }
} })
if (entry.docker_container_name == null) { .catch((error) => {
console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name); console.error("Error pinging Website: ", error);
entry.status = ServiceStatus.error; entry.status = ServiceStatus.error;
} })
});
}
else { entry.status = ServiceStatus.error }
} }
// for non-local locations pinging can be used to see if they're up // If no Location matches
else if (entry.location === ServiceLocation.other) { else {
fetch(entry.href) console.warn("Service Location for Service " + entry.name + " not specified");
.then((data) => { entry.status = ServiceStatus.error;
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;
});
} }
else { entry.status = ServiceStatus.error } return entry;
return; }
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 export default Services