diff --git a/pages/api/containers.tsx b/pages/api/containers.tsx new file mode 100644 index 0000000..44803a2 --- /dev/null +++ b/pages/api/containers.tsx @@ -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' }); + } +} \ No newline at end of file diff --git a/pages/api/services.tsx b/pages/api/services.tsx new file mode 100644 index 0000000..a416716 --- /dev/null +++ b/pages/api/services.tsx @@ -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' }); + } +} + diff --git a/pages/services.tsx b/pages/services.tsx index 2ce661c..251be44 100644 --- a/pages/services.tsx +++ b/pages/services.tsx @@ -1,20 +1,50 @@ import Head from 'next/head' import Link from 'next/link' import styles from '/styles/Home.module.css' -import fsPromises from 'fs/promises' -import path, { resolve } from 'path' -import type { CustomLink, LinkList } from '../interfaces/LinkTypes' +import { Service, CustomLink, EntryList, ServiceStatus, ServiceType, ServiceLocation } from '../interfaces/LinkTypes' 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) { - const serviceList = props.services +//function Services(props: EntryList) { +const Services = () => { + const { serviceList, isLoading, isError } = useServices(); + + let content: ReactElement = <>; + + // TODO: look into asyncing this as well + useStatus(serviceList); + + if (isError) { + content =
Error Loading data
+ } + else if (isLoading) { + content =
Loading..
+ } + else if (serviceList) { + content = +
+ {serviceList.map((item: Service) => ( + + +

{item.name}

+
{item.status}
+

{item.desc}

+

{item.warn}

+
+ + ))} +
+ } return ( <> Neshura Servers +

@@ -24,116 +54,101 @@ function Services(props: LinkList) {

Lists all available Services, most likely up-to-date

-
- {serviceList.map((item: CustomLink) => ( - - -

{item.name}

-
{item.status}
-

{item.desc}

-

{item.warn}

-
- - ))} -
+ + {content} ) } -// Gets a List of all services specified in /public/pages.json -export async function getServerSideProps() { - const filePath = path.join(process.cwd(), '/public/pages.json') - // TODO: look into asyncing this API call - const jsonData = await fsPromises.readFile(filePath) - const list = JSON.parse(jsonData.toString()) - for (let index = 0; index < list.services.length; index++) { - // TODO: look into asyncing this as well - await status(list.services[index]); +function useStatus(serviceList: Service[] | undefined) { + const { data, error } = useSWR('/api/containers', fetcher); + + if (data && serviceList) { + serviceList.forEach((service: Service) => { + getStatus(data, service); + }) + } + else if (error) { + console.log(error); } - return { props: list } } -// reversing this to loop over given entries for every found container would probably improve latency -async function status(entry: CustomLink) { +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 === "brr7-4800u") { + if (entry.location === ServiceLocation.brr7_4800u) { // app in this context means any non-docker page - if (entry.type === "app") { - - let data = new 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.status == 200 || data.status == 301 || data.status == 302) { - return (entry.status = "Online"); - } - else return (entry.status = "Offline"); - } - else return (entry.status = "Offline"); + 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; + } + } + else { + entry.status = ServiceStatus.offline; + } + }) + .catch(error => { + console.log(error); + entry.status = ServiceStatus.error; + }); } else if (entry.type === "docker") { - var Docker = require('dockerode'); - - // 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 - containers.forEach((element: Dockerode.ContainerInfo) => { - element.Names.forEach((containerName: string) => { - if (containerName.startsWith("/")) { - containerName = containerName.substring(1); - } - if (containerName === entry.docker_container_name) { - entry.status = "Online"; - } - }); - if (entry.docker_container_name == null) { - console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name); - entry.status = "ERROR"; - } - }); - }) - ); + entry.status = ServiceStatus.offline; + // Loop over every found container and compare to the entry provided + containers.forEach((element: Dockerode.ContainerInfo) => { + element.Names.forEach((containerName: string) => { + if (containerName.startsWith("/")) { + containerName = containerName.substring(1); + } + if (containerName === entry.docker_container_name) { + entry.status = ServiceStatus.online; + } + }); + if (entry.docker_container_name == null) { + console.log("MISSING DOCKER CONTAINER NAME FOR " + entry.name); + entry.status = ServiceStatus.error; + } + }); } - else { entry.status = "ERROR" } - return; + else { entry.status = ServiceStatus.error } } // for non-local locations pinging can be used to see if they're up - else if (entry.location != "") { - let data: Response; - try { - data = await fetch(entry.href); - } - catch (e) { - console.log(e) - return (entry.status = "Offline"); - } - - if (data.ok) { - if (data.status == 200 || data.status == 301 || data.status == 302) { - return (entry.status = "Online"); - } - else return (entry.status = "Offline"); - } - else return (entry.status = "Offline"); + 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; + }); } - else { return (entry.status = "ERROR") } + else { entry.status = ServiceStatus.error } + return; } export default Services \ No newline at end of file