Large Code Rewrite

Added 2 API Endpoints for use with useSWR, removed the service and status fetching from getStaticProps, rewrote status fetching to use async (WIP, needs testing)
This commit is contained in:
Neshura 2022-12-10 03:02:03 +01:00
parent 89a8278dbe
commit 6af8cb3e1b
No known key found for this signature in database
GPG key ID: ACDF5B6EBECF6B0A
3 changed files with 157 additions and 99 deletions

22
pages/api/containers.tsx Normal file
View file

@ -0,0 +1,22 @@
import fsPromises from 'fs/promises'
import path from 'path'
import { Service, ServiceStatus } from '../../interfaces/LinkTypes';
export default async function ServicesAPI(req: any, res: any) {
try {
var Docker = require('dockerode');
const options = {
socketPath: '/var/run/docker.sock',
path: '/v1.41/containers/json'
};
var docker = new Docker({ socketPath: options.socketPath });
const list = await docker.listContainers({ all: true })
res.status(200).json(list);
}
catch (error) {
console.log(error);
res.status(500).json({ error: 'Error reading data' });
}
}

21
pages/api/services.tsx Normal file
View file

@ -0,0 +1,21 @@
import fsPromises from 'fs/promises'
import path from 'path'
import { Service, ServiceStatus } from '../../interfaces/LinkTypes';
export default async function ServicesAPI(req: any, res: any) {
try {
const filePath = path.join(process.cwd(), '/public/pages.json')
const data = await fsPromises.readFile(filePath)
.then((file) => JSON.parse(file.toString()));
data.services.forEach((service: Service) => {
service.status = ServiceStatus.loading;
});
res.status(200).json(data.services);
}
catch (error) {
console.log(error);
res.status(500).json({ error: 'Error reading data' });
}
}

View file

@ -1,20 +1,50 @@
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 fsPromises from 'fs/promises' import { Service, CustomLink, EntryList, ServiceStatus, ServiceType, ServiceLocation } from '../interfaces/LinkTypes'
import path, { resolve } from 'path'
import type { CustomLink, LinkList } from '../interfaces/LinkTypes'
import Dockerode from 'dockerode'; import Dockerode from 'dockerode';
import { ReactElement, useEffect, useState } from 'react'
import useSWR, { KeyedMutator } from 'swr';
const fetcher = (url: string) => fetch(url).then((res) => res.json())
function Services(props: LinkList) { //function Services(props: EntryList) {
const serviceList = props.services const Services = () => {
const { serviceList, isLoading, isError } = useServices();
let content: ReactElement = <></>;
// TODO: look into asyncing this as well
useStatus(serviceList);
if (isError) {
content = <div className={styles.grid}>Error Loading data</div>
}
else if (isLoading) {
content = <div className={styles.grid}>Loading..</div>
}
else if (serviceList) {
content =
<div className={styles.grid}>
{serviceList.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>
}
return ( return (
<> <>
<Head> <Head>
<title>Neshura Servers</title> <title>Neshura Servers</title>
<meta charSet='utf-8' /> <meta charSet='utf-8' />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="description" content="Lists all available Services, most likely up-to-date" />
</Head> </Head>
<h1 className={styles.title}> <h1 className={styles.title}>
@ -24,75 +54,62 @@ function Services(props: LinkList) {
<p className={styles.description}> <p className={styles.description}>
Lists all available Services, most likely up-to-date Lists all available Services, most likely up-to-date
</p> </p>
<div className={styles.grid}>
{serviceList.map((item: CustomLink) => ( {content}
<Link key={item.name} href={item.href}>
<a className={styles.contentcard}>
<div className={styles.contenttitle}><h2>{item.name}</h2></div>
<div className={item.status == "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>
</> </>
) )
} }
// Gets a List of all services specified in /public/pages.json function useStatus(serviceList: Service[] | undefined) {
export async function getServerSideProps() { const { data, error } = useSWR('/api/containers', fetcher);
const filePath = path.join(process.cwd(), '/public/pages.json')
// TODO: look into asyncing this API call if (data && serviceList) {
const jsonData = await fsPromises.readFile(filePath) serviceList.forEach((service: Service) => {
const list = JSON.parse(jsonData.toString()) getStatus(data, service);
for (let index = 0; index < list.services.length; index++) { })
// TODO: look into asyncing this as well }
await status(list.services[index]); else if (error) {
console.log(error);
} }
return { props: list }
} }
// reversing this to loop over given entries for every found container would probably improve latency function useServices(): { serviceList: Service[] | undefined, isLoading: boolean, isError: boolean } {
async function status(entry: CustomLink) { 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 // for now only the BRR7-4800U can be used with Docker, needs changing once more Servers are used
// TODO: support multiple locations for docker // TODO: support multiple locations for docker
if (entry.location === "brr7-4800u") { if (entry.location === ServiceLocation.brr7_4800u) {
// app in this context means any non-docker page // app in this context means any non-docker page
if (entry.type === "app") { if (entry.type === ServiceType.app) {
fetch(entry.href)
let data = new Response(); .then((data: Response) => {
try {
await fetch(entry.href).then((response: Response) => data = response);
}
catch (e) {
console.log(e)
return (entry.status = "Offline");
}
if (data.ok) { if (data.ok) {
if (data.status == 200 || data.status == 301 || data.status == 302) { if (data.status == 200 || data.status == 301 || data.status == 302) {
return (entry.status = "Online"); entry.status = ServiceStatus.online;
} }
else return (entry.status = "Offline"); else {
entry.status = ServiceStatus.offline;
} }
else return (entry.status = "Offline"); }
else {
entry.status = ServiceStatus.offline;
}
})
.catch(error => {
console.log(error);
entry.status = ServiceStatus.error;
});
} }
else if (entry.type === "docker") { else if (entry.type === "docker") {
var Docker = require('dockerode'); entry.status = ServiceStatus.offline;
// TODO: read these paths from some config instead of hardcoding them
const options = {
socketPath: '/var/run/docker.sock',
path: '/v1.41/containers/json'
};
var docker = new Docker({ socketPath: options.socketPath });
// default is set as Offline, prevents uncaught cases without set status
entry.status = "Offline";
// TODO: async possible?
await docker.listContainers({all: true}).then(
((containers: Dockerode.ContainerInfo[]) => {
// Loop over every found container and compare to the entry provided // Loop over every found container and compare to the entry provided
containers.forEach((element: Dockerode.ContainerInfo) => { containers.forEach((element: Dockerode.ContainerInfo) => {
element.Names.forEach((containerName: string) => { element.Names.forEach((containerName: string) => {
@ -100,40 +117,38 @@ async function status(entry: CustomLink) {
containerName = containerName.substring(1); containerName = containerName.substring(1);
} }
if (containerName === entry.docker_container_name) { if (containerName === entry.docker_container_name) {
entry.status = "Online"; entry.status = ServiceStatus.online;
} }
}); });
if (entry.docker_container_name == null) { if (entry.docker_container_name == null) {
console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name); console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name);
entry.status = "ERROR"; entry.status = ServiceStatus.error;
} }
}); });
})
);
} }
else { entry.status = "ERROR" } else { entry.status = ServiceStatus.error }
return;
} }
// for non-local locations pinging can be used to see if they're up // for non-local locations pinging can be used to see if they're up
else if (entry.location != "") { else if (entry.location === ServiceLocation.other) {
let data: Response; fetch(entry.href)
try { .then((data) => {
data = await fetch(entry.href);
}
catch (e) {
console.log(e)
return (entry.status = "Offline");
}
if (data.ok) { if (data.ok) {
if (data.status == 200 || data.status == 301 || data.status == 302) { if (data.status == 200 || data.status == 301 || data.status == 302) {
return (entry.status = "Online"); return (entry.status = ServiceStatus.online);
} }
else return (entry.status = "Offline"); else (entry.status = ServiceStatus.offline);
} }
else return (entry.status = "Offline"); else {
entry.status = ServiceStatus.offline;
} }
else { return (entry.status = "ERROR") } })
.catch((error) => {
console.log(error);
entry.status = ServiceStatus.error;
});
}
else { entry.status = ServiceStatus.error }
return;
} }
export default Services export default Services