incremental Data Fetching using useSWR
not quite like I want but should be good enough
This commit is contained in:
parent
fff04d019a
commit
3303008ec3
1 changed files with 127 additions and 87 deletions
|
@ -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,42 +75,24 @@ 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) {
|
||||||
|
case 200:
|
||||||
|
case 301:
|
||||||
|
case 302:
|
||||||
entry.status = ServiceStatus.online;
|
entry.status = ServiceStatus.online;
|
||||||
}
|
break;
|
||||||
else {
|
default:
|
||||||
entry.status = ServiceStatus.offline;
|
entry.status = ServiceStatus.offline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,53 +100,96 @@ function getStatus(containers: Dockerode.ContainerInfo[], entry: Service) {
|
||||||
entry.status = ServiceStatus.offline;
|
entry.status = ServiceStatus.offline;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
|
||||||
console.log(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;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
entry.status = ServiceStatus.offline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entry.docker_container_name == null) {
|
|
||||||
console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name);
|
|
||||||
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) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.error("Error pinging Website: ", error);
|
||||||
entry.status = ServiceStatus.error;
|
entry.status = ServiceStatus.error;
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
else { entry.status = ServiceStatus.error }
|
// Type Docker
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error pinging Website: ", error);
|
||||||
|
entry.status = ServiceStatus.error;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// If no Location matches
|
||||||
|
else {
|
||||||
|
console.warn("Service Location for Service " + entry.name + " not specified");
|
||||||
|
entry.status = ServiceStatus.error;
|
||||||
|
}
|
||||||
|
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
|
export default Services
|
Loading…
Reference in a new issue