Rewrite page in Svelte 5 #49

Merged
Neshura merged 94 commits from rewrite/svelte into main 2024-01-06 14:12:09 +00:00
7 changed files with 247 additions and 60 deletions
Showing only changes of commit abea3f1be9 - Show all commits

View file

@ -4,14 +4,14 @@
import { OpenInNewWindow } from 'radix-icons-svelte'; import { OpenInNewWindow } from 'radix-icons-svelte';
import { quintInOut } from 'svelte/easing'; import { quintInOut } from 'svelte/easing';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { IconType, type Service } from '$lib/types/data-types'; import { IconType, type Server } from '$lib/types/data-types';
import { Skeleton } from '$lib/components/ui/skeleton'; import { Skeleton } from '$lib/components/ui/skeleton';
import type { Heartbeat } from '$lib/types/uptime-kuma-types'; import type { Heartbeat } from '$lib/types/uptime-kuma-types';
let { service, icons, monitor } = $props<{ let { server, icons, monitor } = $props<{
service: Service; server: Server;
icons: Array<string>; icons: Array<string>;
monitor: Heartbeat; monitor?: Heartbeat;
}>(); }>();
let status = $state(4); let status = $state(4);
@ -24,32 +24,36 @@
let img_source: string = $state(''); let img_source: string = $state('');
function checkForImage(service: Service) { function checkForImage(server: Server) {
const rootSplit = service.icon.split('/'); const rootSplit = server.icon.split('/');
const root = rootSplit[rootSplit.length - 1]; const root = rootSplit[rootSplit.length - 1];
if (icons.includes(`${root}.${service.iconType}`)) { if (icons.includes(`${root}.${server.iconType}`)) {
img_source = `${service.icon}.${service.iconType}`; img_source = `${server.icon}.${server.iconType}`;
} else { } else {
img_source = ''; img_source = '';
} }
} }
$effect(() => { $effect(() => {
if (typeof server.id === 'undefined') {
status = 99;
}
if (typeof monitor !== 'undefined') { if (typeof monitor !== 'undefined') {
status = monitor.status; status = monitor.status;
} }
if (icons.length != 0) { if (icons.length != 0 && typeof server.icon !== "undefined") {
const rootSplit = service.icon.split('/'); const rootSplit = server.icon.split('/');
const root = rootSplit[rootSplit.length - 1]; const root = rootSplit[rootSplit.length - 1];
if (service.iconType === IconType.SVG) { if (server.iconType === IconType.SVG) {
checkForImage(service); checkForImage(server);
}
else {
if (icons.includes(`${root}-36.${server.iconType}`)) {
img_source = `${server.icon}-36.${server.iconType}`;
} else { } else {
if (icons.includes(`${root}-36.${service.iconType}`)) { checkForImage(server);
img_source = `${service.icon}-36.${service.iconType}`;
} else {
checkForImage(service);
} }
} }
} }
@ -58,7 +62,8 @@
<div <div
class="flex h-48 w-[28rem] flex-col gap-y-2 rounded-xl border-t-4 class="flex h-48 w-[28rem] flex-col gap-y-2 rounded-xl border-t-4
{status == 0 {status == 99 ? 'border-primary'
: status == 0
? 'border-offline' ? 'border-offline'
: status == 1 : status == 1
? 'border-online' ? 'border-online'
@ -75,22 +80,22 @@
on:mouseover={() => (hover.title = true)} on:mouseover={() => (hover.title = true)}
on:mouseleave={() => (hover.title = false)} on:mouseleave={() => (hover.title = false)}
> >
{#if service.icon} {#if typeof server.icon !== "undefined"}
{#if img_source != ''} {#if img_source != ''}
<img <img
width="24px" width="24px"
class="h-6 w-6 cursor-pointer" class="h-6 w-6 cursor-pointer"
src={img_source} src={img_source}
alt="{service.name} Logo" alt="{server.name} Logo"
/> />
{:else} {:else}
<Skeleton class="h-6 w-6 rounded-full" /> <Skeleton class="h-6 w-6 rounded-full" />
{/if} {/if}
{:else}{/if} {:else}{/if}
<a <a
href={service.href} href={server.href}
class="font-bold {!hover.title || 'text-secondary'}" class="font-bold {!hover.title || 'text-secondary'}"
>{service.name}</a >{server.name}</a
> >
{#if hover.title} {#if hover.title}
<div <div
@ -105,6 +110,7 @@
{/if} {/if}
</div> </div>
{#if typeof server.id !== "undefined"}
<h1 <h1
class="w-16 rounded-md border-b-2 class="w-16 rounded-md border-b-2
{status == 0 {status == 0
@ -137,13 +143,14 @@
? 'Maint.' ? 'Maint.'
: 'Loading'} : 'Loading'}
</h1> </h1>
{/if}
</div> </div>
<p class="text-wrap text-center text-sm">{service.desc}</p> <p class="text-wrap text-center text-sm">{server.desc}</p>
<p class="text-center text-sm font-bold text-destructive">{service.warn}</p> <p class="text-center text-sm font-bold text-destructive">{server.warn}</p>
<div class="grid {service.extLink ? 'grid-cols-2' : 'grid-cols-1'} mt-auto justify-items-center"> <div class="grid {server.extLink ? 'grid-cols-2' : 'grid-cols-1'} mt-auto justify-items-center">
<a <a
class="flex flex-row rounded-md border-x-2 px-2 text-sm hover:border-secondary hover:text-secondary" class="flex flex-row rounded-md border-x-2 px-2 text-sm hover:border-secondary hover:text-secondary"
href={service.href} href={server.href}
on:mouseover={() => (hover.link = true)} on:mouseover={() => (hover.link = true)}
on:mouseleave={() => (hover.link = false)} on:mouseleave={() => (hover.link = false)}
> >
@ -160,10 +167,10 @@
</div> </div>
{/if} {/if}
</a> </a>
{#if service.extLink} {#if server.extLink}
<a <a
class="flex flex-row rounded-md border-x-2 px-2 text-sm hover:border-secondary hover:text-secondary" class="flex flex-row rounded-md border-x-2 px-2 text-sm hover:border-secondary hover:text-secondary"
href={service.extLink} href={server.extLink}
on:mouseover={() => (hover.ext = true)} on:mouseover={() => (hover.ext = true)}
on:mouseleave={() => (hover.ext = false)} on:mouseleave={() => (hover.ext = false)}
> >

View file

@ -1,7 +1,7 @@
export type Service = { export type Service = {
readonly name: string; readonly name: string;
readonly icon: string; readonly icon?: string;
readonly iconType: IconType; readonly iconType?: IconType;
readonly href: string; readonly href: string;
readonly desc: string; readonly desc: string;
readonly warn: string; readonly warn: string;
@ -9,6 +9,17 @@ export type Service = {
readonly id: number; readonly id: number;
}; };
export type Server = {
readonly name: string;
readonly icon?: string;
readonly iconType?: IconType;
readonly connection?: string;
readonly href?: string;
readonly desc?: string;
readonly extLink?: string;
readonly id?: number;
};
export enum IconType { export enum IconType {
SVG = 'svg', SVG = 'svg',
AVIF = 'avif', AVIF = 'avif',

View file

@ -0,0 +1,10 @@
import * as fs from 'fs';
import { json } from '@sveltejs/kit';
export function GET() {
const content = fs.readFileSync('static/data/servers.json').toString();
const data = JSON.parse(content);
return json(data);
}

View file

@ -4,7 +4,7 @@ import { json } from '@sveltejs/kit';
export function GET() { export function GET() {
const content = fs.readFileSync('static/data/services.json').toString(); const content = fs.readFileSync('static/data/services.json').toString();
let data = JSON.parse(content); const data = JSON.parse(content);
return json(data); return json(data);
} }

View file

@ -0,0 +1,62 @@
import { io, Socket } from 'socket.io-client';
import * as fs from 'fs';
export async function load() {
const promise = getJwt();
return {
promise
};
}
async function getJwt(): Promise<string> {
const socket = io('https://status.neshweb.net/');
const credFile = './credentials.json';
let token = '';
let valid = false;
if (fs.existsSync(credFile)) {
const content = fs.readFileSync(credFile);
token = content.toString();
}
socket.on('connect', async () => {
if (token == '') {
token = await login(socket);
valid = true;
} else {
socket.emit('loginByToken', token, async (res) => {
if (!res.ok) {
token = await login(socket);
}
valid = true;
});
}
});
while (!valid) {
await new Promise((resolve) => setTimeout(resolve, 10));
}
fs.writeFileSync(credFile, token);
return token;
}
async function login(socket: Socket): Promise<string> {
let token = '';
socket.emit(
'login',
{ username: process.env.KUMA_USERNAME, password: process.env.KUMA_PASSWORD, token: '' },
(res: { token: string }) => {
token = res.token;
socket.close();
}
);
while (token == '') {
await new Promise((resolve) => setTimeout(resolve, 10));
}
return token;
}

View file

@ -1,4 +1,106 @@
<svelte:options runes={true} />
<script lang="ts">
import type { Server } from '$lib/types/data-types';
import { io } from 'socket.io-client';
import type { Heartbeat } from '$lib/types/uptime-kuma-types';
import ServerCard from "$lib/components/ServerCard.svelte";
let { data }: { data: { promise: Promise<string> } } = $props();
let token = $state();
data.promise.then((jwt) => {
token = jwt;
});
let servers: readonly Server[] = $state.frozen([]);
let icons: readonly string[] = $state.frozen([]);
let monitorList: Map<number, Heartbeat> = $state(new Map());
//let token = $props();
$effect(() => {
if (token) {
const socket = io('https://status.neshweb.net/');
socket.on('connect', () => {
socket.emit('loginByToken', token, () => {});
});
socket.on('importantHeartbeatList', (_, data) => {
let recent = data[data.length - 1];
let monitor: Heartbeat = {
monitorID: recent.monitor_id,
status: recent.status,
time: recent.time,
msg: recent.msg,
ping: recent.ping,
important: recent.important,
duration: recent.duration
};
monitorList.set(monitor.monitorID, monitor);
monitorList = new Map(monitorList.entries());
})
socket.on('heartbeatList', (_, data) => {
let recent = data[data.length - 1];
let monitor: Heartbeat = {
monitorID: recent.monitor_id,
status: recent.status,
time: recent.time,
msg: recent.msg,
ping: recent.ping,
important: recent.important,
duration: recent.duration
};
monitorList.set(monitor.monitorID, monitor);
monitorList = new Map(monitorList.entries());
});
socket.on('heartbeat', (data) => {
monitorList.set(data.monitorID, data);
monitorList = new Map(monitorList.entries());
});
}
});
async function get(url: string): Promise<any> {
let res = await fetch(url);
if (res.ok) {
let data = await res.json();
return data;
} else {
return Promise.reject();
}
}
$effect(() => {
get('/data/servers').then((data: Server[]) => {
servers = data;
});
});
$effect(() => {
get('/assets/icons').then((data: string[]) => {
icons = data;
});
});
</script>
<svelte:head> <svelte:head>
<title>Servers</title> <title>Servers</title>
<meta name="description" content="Overview of Game Servers running on neshweb.net" /> <meta name="description" content="Overview of Game Servers running on neshweb.net" />
</svelte:head> </svelte:head>
<div class="flex max-h-full flex-row flex-wrap justify-center gap-10 overflow-auto p-8">
{#each servers as server}
{#if typeof server.id === 'undefined'}
<ServerCard {server} {icons} />
{:else}
<ServerCard {server} {icons} monitor={monitorList.get(server.id)} />
{/if}
{/each}
</div>

View file

@ -3,29 +3,24 @@
"name": "Minecraft", "name": "Minecraft",
"icon": "/assets/icons/minecraft-logo", "icon": "/assets/icons/minecraft-logo",
"iconType": "avif", "iconType": "avif",
"connection": "minecraft.neshweb.net",
"href": "https://minecraft.neshweb.net/", "href": "https://minecraft.neshweb.net/",
"desc": "View all currently available Minecraft Servers and their mods" "desc": "View all currently available Minecraft Servers and their mods",
"id": 38
}, },
{ {
"name": "Ready or Not", "name": "Ready or Not",
"icon": "/assets/icons/ron-logo", "icon": "/assets/icons/ron-logo",
"iconType": "avif", "iconType": "avif",
"connection": "minecraft.neshweb.net",
"href": "https://readyornot.neshweb.net/", "href": "https://readyornot.neshweb.net/",
"desc": "Collection of Floor Plans for the Game 'Ready or Not'" "desc": "Collection of Floor Plans for the Game 'Ready or Not'"
}, },
{ {
"name": "Zomboid", "name": "Factorio"
"icon": "/assets/icons/zomboid-logo",
"iconType": "avif",
"ip": "91.13.248.30",
"status": "Online"
},
{
"name": "Factorio",
"status": "Online"
}, },
{ {
"name": "Space Engineers", "name": "Space Engineers",
"status": "Online" "id": 13
} }
] ]