Content Migration from websites repo
This commit is contained in:
parent
c1d93528d7
commit
ba88a7adc5
22 changed files with 8767 additions and 1 deletions
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# do not track installed modules
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# do not track built files
|
||||||
|
/.next
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/data
|
||||||
|
/confs
|
50
Dockerfile
Normal file
50
Dockerfile
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
## INIT STEP
|
||||||
|
# Install dependencies only when needed
|
||||||
|
FROM node:16-alpine AS deps
|
||||||
|
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the files needed to install deps
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
RUN yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
## BUILD STEP
|
||||||
|
# Rebuild the source code only when needed
|
||||||
|
FROM node:16-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy node_modules installed by the deps step
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
## RUN STEP
|
||||||
|
FROM node:16-alpine AS runner
|
||||||
|
|
||||||
|
LABEL author="neshura@proton.me"
|
||||||
|
WORKDIR /usr/src/ap
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
|
||||||
|
# Automatically leverage output traces to reduce image size
|
||||||
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
# expose port 3000
|
||||||
|
ENV PORT 3000
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "yarn", "start" ]
|
|
@ -1 +0,0 @@
|
||||||
# www
|
|
11
components/footer.tsx
Normal file
11
components/footer.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import styles from '/styles/Home.module.css'
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
return (
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
Built using Next.js
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer;
|
37
components/layout.tsx
Normal file
37
components/layout.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import Footer from './footer'
|
||||||
|
import Navbar from './navbar'
|
||||||
|
import styles from '/styles/Home.module.css'
|
||||||
|
import Script from 'next/script'
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<Script id="matomo_analytics">
|
||||||
|
{`
|
||||||
|
var _paq = window._paq = window._paq || [];
|
||||||
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
|
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
|
||||||
|
_paq.push(["setCookieDomain", "www.neshura-server.net"]);
|
||||||
|
_paq.push(["disableCookies"]);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="//temp.neshura-server.net/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||||
|
_paq.push(['setSiteId', '2']);
|
||||||
|
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
|
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||||
|
})();
|
||||||
|
`}
|
||||||
|
</Script>
|
||||||
|
|
||||||
|
<Navbar />
|
||||||
|
<main className={styles.main}>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
30
components/navbar.tsx
Normal file
30
components/navbar.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import styles from '/styles/Home.module.css'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ name: "Home", href: "/" },
|
||||||
|
{ name: "About", href: "/about" },
|
||||||
|
{ name: "Games", href: "/games" },
|
||||||
|
{ name: "Services", href: "/services" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={styles.navbar}>
|
||||||
|
{navLinks.map((item, index) => (
|
||||||
|
<Link key={item.name} href={item.href}>
|
||||||
|
<a className={router.pathname == item.href ? styles.navelem_active : styles.navelem}>{item.name}</a>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link key="Mastodon_Verify" href="https://mastodon.neshura-server.net/@neshura">
|
||||||
|
<a className={styles.navelem} rel="me" href="https://mastodon.neshura-server.net/@neshura">Mastodon</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navbar;
|
15
interfaces/LinkTypes.ts
Normal file
15
interfaces/LinkTypes.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export interface LinkList {
|
||||||
|
services: CustomLink[],
|
||||||
|
games: CustomLink[]
|
||||||
|
}
|
||||||
|
export interface CustomLink {
|
||||||
|
type: string,
|
||||||
|
name: string,
|
||||||
|
href: string,
|
||||||
|
desc: string,
|
||||||
|
warn: string,
|
||||||
|
ip: string,
|
||||||
|
location: string,
|
||||||
|
status: string,
|
||||||
|
docker_container_name: string
|
||||||
|
}
|
8
next.config.js
Normal file
8
next.config.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
output: 'standalone',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
5908
package-lock.json
generated
Normal file
5908
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
29
package.json
Normal file
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "www",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/dockerode": "^3.3.14",
|
||||||
|
"@types/next": "^9.0.0",
|
||||||
|
"dockerode": "^3.3.4",
|
||||||
|
"next": "^12.3.0",
|
||||||
|
"node": "^18.9.0",
|
||||||
|
"node-html-parser": "^5.3.3",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"swr": "^1.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.7.18",
|
||||||
|
"@types/react": "^18.0.14",
|
||||||
|
"eslint": "^8.23.1",
|
||||||
|
"eslint-config-next": "12.2.0",
|
||||||
|
"typescript": "^4.7.4"
|
||||||
|
}
|
||||||
|
}
|
22
pages/_app.tsx
Normal file
22
pages/_app.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import '/styles/globals.css'
|
||||||
|
import type { ReactElement, ReactNode } from 'react'
|
||||||
|
import Layout from '../components/layout'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
import { AppProps } from 'next/app';
|
||||||
|
|
||||||
|
|
||||||
|
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
|
||||||
|
getLayout?: (page: ReactElement) => ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AppPropsWithLayout = AppProps & {
|
||||||
|
Component: NextPageWithLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Website({ Component, pageProps }: AppPropsWithLayout) {
|
||||||
|
// Use the layout defined at the page level, if available
|
||||||
|
const getLayout = Component.getLayout ?? ((page) => (
|
||||||
|
<Layout>{page}</Layout>))
|
||||||
|
|
||||||
|
return getLayout(<Component {...pageProps} />)
|
||||||
|
}
|
20
pages/about.tsx
Normal file
20
pages/about.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import styles from '/styles/Home.module.css'
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Neshura Servers</title>
|
||||||
|
<meta charSet='utf-8' />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<h1 className={styles.title}>
|
||||||
|
About
|
||||||
|
</h1>
|
||||||
|
<p className={styles.description}>
|
||||||
|
This website is primarily for managing my game servers in one spot
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
56
pages/api/zomboid.tsx
Normal file
56
pages/api/zomboid.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import fsPromises from 'fs/promises'
|
||||||
|
import path from 'path'
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { CustomLink } from '../../interfaces/LinkTypes';
|
||||||
|
|
||||||
|
export default async function userHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const {
|
||||||
|
body: { ip, status, key },
|
||||||
|
method,
|
||||||
|
} = req
|
||||||
|
|
||||||
|
const filePath = path.join(process.cwd(), '/data/zomboid.json')
|
||||||
|
const jsonBuffer = await fsPromises.readFile(filePath)
|
||||||
|
const jsonData = JSON.parse(jsonBuffer.toString())
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'GET':
|
||||||
|
res.status(200).json(jsonData)
|
||||||
|
break
|
||||||
|
case 'PUT':
|
||||||
|
if (key == "#inFamous3") {
|
||||||
|
if (ip != jsonData.currentIP) {
|
||||||
|
jsonData.historicIP.unshift(jsonData.currentIP)
|
||||||
|
if (Object.keys(jsonData.historicIP).length > 10) {
|
||||||
|
jsonData.historicIP.pop()
|
||||||
|
}
|
||||||
|
jsonData.currentIP = ip
|
||||||
|
}
|
||||||
|
jsonData.status = status
|
||||||
|
|
||||||
|
modifyServerStatus(ip, status)
|
||||||
|
|
||||||
|
|
||||||
|
await fsPromises.writeFile(filePath, JSON.stringify(jsonData, null, 2))
|
||||||
|
|
||||||
|
res.status(200).json(jsonData)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.status(401).json("wrong key")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
res.setHeader('Allow', ['GET', 'PUT'])
|
||||||
|
res.status(405).end(`Method ${method} Not Allowed`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function modifyServerStatus(ip: String, status: String) {
|
||||||
|
const filePath = path.join(process.cwd(), '/confs/pages.json')
|
||||||
|
const jsonBuffer = await fsPromises.readFile(filePath)
|
||||||
|
const data = JSON.parse(jsonBuffer.toString())
|
||||||
|
data.servers.zomboid.ip = ip
|
||||||
|
data.servers.zomboid.status = status
|
||||||
|
await fsPromises.writeFile(filePath, JSON.stringify(data, null, 2))
|
||||||
|
return data
|
||||||
|
}
|
65
pages/games.tsx
Normal file
65
pages/games.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import styles from '/styles/Home.module.css'
|
||||||
|
import fsPromises from 'fs/promises'
|
||||||
|
import path from 'path'
|
||||||
|
import type { CustomLink, LinkList } from '../interfaces/LinkTypes'
|
||||||
|
|
||||||
|
function Servers(props: LinkList) {
|
||||||
|
const serverList = props.games
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Neshura Servers</title>
|
||||||
|
<meta charSet='utf-8' />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<h1 className={styles.title}>
|
||||||
|
Server List
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className={styles.description}>
|
||||||
|
Lists all available Services, probably up-to-date
|
||||||
|
</p>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{Object.values(serverList).map((item: CustomLink) => {
|
||||||
|
if (item.href != null) {
|
||||||
|
return (
|
||||||
|
<Link key={item.name} href={item.href}>
|
||||||
|
<a className={styles.contentcard}>
|
||||||
|
<div className={styles.contenttitle}><h2>{item.name }</h2></div>
|
||||||
|
<div><p>{item.desc}</p></div>
|
||||||
|
<div><p>{item.ip }</p></div>
|
||||||
|
<div className={item.status == "Online" ? styles.contentonline : styles.contentoffline}><p>{item.status}</p></div>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<a key={item.name} className={styles.contentcardstatic}>
|
||||||
|
<div className={styles.contenttitle}><h2>{item.name }</h2></div>
|
||||||
|
<div><p>{item.desc}</p></div>
|
||||||
|
<div><p>{item.ip}</p></div>
|
||||||
|
<div className={item.status == "Online" ? styles.contentonline : styles.contentoffline}><p>{item.status}</p></div>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServerSideProps() {
|
||||||
|
const filePath = path.join(process.cwd(), '/confs/pages.json')
|
||||||
|
const jsonData = await fsPromises.readFile(filePath)
|
||||||
|
const list = JSON.parse(jsonData.toString())
|
||||||
|
|
||||||
|
return { props: list }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Servers
|
46
pages/index.tsx
Normal file
46
pages/index.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import styles from '/styles/Home.module.css'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Neshura Servers</title>
|
||||||
|
<meta charSet='utf-8' />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<h1 className={styles.title}>
|
||||||
|
Welcome to my Servers Webpage
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className={styles.description}>
|
||||||
|
Feel free to look around
|
||||||
|
</p>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
<Link key="about" href="/about">
|
||||||
|
<a className={styles.card}>
|
||||||
|
<h2>About →</h2>
|
||||||
|
<p>Useless Info, don't bother</p>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link key="servers" href="/games">
|
||||||
|
<a className={styles.card}>
|
||||||
|
<h2>Games →</h2>
|
||||||
|
<p>List of all available Servers</p>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link key="services" href="/services">
|
||||||
|
<a className={styles.card}>
|
||||||
|
<h2>Services →</h2>
|
||||||
|
<p>List of available Services</p>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
139
pages/services.tsx
Normal file
139
pages/services.tsx
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
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 Dockerode from 'dockerode';
|
||||||
|
|
||||||
|
|
||||||
|
function Services(props: LinkList) {
|
||||||
|
const serviceList = props.services
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Neshura Servers</title>
|
||||||
|
<meta charSet='utf-8' />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<h1 className={styles.title}>
|
||||||
|
Service List
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className={styles.description}>
|
||||||
|
Lists all available Services, most likely up-to-date
|
||||||
|
</p>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{serviceList.map((item: CustomLink) => (
|
||||||
|
<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 /confs/pages.json
|
||||||
|
export async function getServerSideProps() {
|
||||||
|
const filePath = path.join(process.cwd(), './confs/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]);
|
||||||
|
}
|
||||||
|
return { props: list }
|
||||||
|
}
|
||||||
|
|
||||||
|
// reversing this to loop over given entries for every found container would probably improve latency
|
||||||
|
async function status(entry: CustomLink) {
|
||||||
|
// 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") {
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else { entry.status = "ERROR" }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 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 { return (entry.status = "ERROR") }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Services
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
207
styles/Home.module.css
Normal file
207
styles/Home.module.css
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--black-0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
color: var(--def-blue);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 1rem 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
padding: 2rem 0;
|
||||||
|
border-bottom: 1px solid var(--def-blue);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.navelem {
|
||||||
|
width: auto;
|
||||||
|
color: var(--def-blue);
|
||||||
|
margin: 0.2rem;
|
||||||
|
border: 1px solid;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navelem:hover {
|
||||||
|
color: var(--def-orange);
|
||||||
|
background-color: var(--black-1e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navelem_active {
|
||||||
|
width: auto;
|
||||||
|
color: var(--def-orange);
|
||||||
|
margin: 0.2rem;
|
||||||
|
border: 1px solid;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
color: var(--def-blue);
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
padding: 2rem 0;
|
||||||
|
border-top: 1px solid var(--def-blue);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title a {
|
||||||
|
color: var(--def-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title a:hover,
|
||||||
|
.title a:focus,
|
||||||
|
.title a:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title,
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: 4rem 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background: var(--black-1e);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||||
|
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentoffline {
|
||||||
|
color: var(--def-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentonline {
|
||||||
|
color: var(--def-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentcard {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid var(--black-2d);
|
||||||
|
border-radius: 10px;
|
||||||
|
border-color: var(--def-blue);
|
||||||
|
transition: color 0.15s ease, border-color 0.15s ease;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentcardstatic {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid var(--black-2d);
|
||||||
|
border-radius: 10px;
|
||||||
|
border-color: var(--def-blue);
|
||||||
|
transition: color 0.15s ease, border-color 0.15s ease;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentcard:hover,
|
||||||
|
.contentcard:focus,
|
||||||
|
.contentcard:active {
|
||||||
|
color: var(--def-orange);
|
||||||
|
border-color: var(--def-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: left;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid var(--black-2d);
|
||||||
|
border-radius: 10px;
|
||||||
|
border-color: var(--def-blue);
|
||||||
|
transition: color 0.15s ease, border-color 0.15s ease;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover,
|
||||||
|
.card:focus,
|
||||||
|
.card:active {
|
||||||
|
color: var(--def-orange);
|
||||||
|
border-color: var(--def-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card h2 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardwarn {
|
||||||
|
color: var(--def-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 1em;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.grid {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
27
styles/globals.css
Normal file
27
styles/globals.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--black-0f: #0f0f0f;
|
||||||
|
--black-1e: #1e1e1e;
|
||||||
|
--black-2d: #2d2d2d;
|
||||||
|
|
||||||
|
--def-blue: #00aaff;
|
||||||
|
--def-orange: #ff5300;
|
||||||
|
--def-red: #ff0000;
|
||||||
|
--def-green: #00ff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"incremental": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue