add realtime statistics!!!
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled

This commit is contained in:
Lee 2024-04-18 01:21:38 +01:00
parent f5203f9742
commit defdcccdb6
14 changed files with 143 additions and 9 deletions

4
.fleet/settings.json Normal file
View File

@ -0,0 +1,4 @@
{
"toolchains": [],
"nodejs.editor.formatOnSave.prettier.mode": "Enabled"
}

12
.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"jsxBracketSameLine": false,
"printWidth": 120,
"proseWrap": "always",
"quoteProps": "as-needed",
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false
}

View File

@ -22,7 +22,9 @@
"next": "14.2.1",
"next-themes": "^0.3.0",
"react": "^18",
"react-countup": "^6.5.3",
"react-dom": "^18",
"react-use-websocket": "3.0.0",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7"
},

29
pnpm-lock.yaml generated
View File

@ -44,9 +44,15 @@ dependencies:
react:
specifier: ^18
version: 18.2.0
react-countup:
specifier: ^6.5.3
version: 6.5.3(react@18.2.0)
react-dom:
specifier: ^18
version: 18.2.0(react@18.2.0)
react-use-websocket:
specifier: 3.0.0
version: 3.0.0(react-dom@18.2.0)(react@18.2.0)
tailwind-merge:
specifier: ^2.2.2
version: 2.2.2
@ -2064,6 +2070,10 @@ packages:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: false
/countup.js@2.8.0:
resolution: {integrity: sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ==}
dev: false
/create-jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2):
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -4446,6 +4456,15 @@ packages:
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
/react-countup@6.5.3(react@18.2.0):
resolution: {integrity: sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==}
peerDependencies:
react: '>= 16.3.0'
dependencies:
countup.js: 2.8.0
react: 18.2.0
dev: false
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
@ -4464,6 +4483,16 @@ packages:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
dev: false
/react-use-websocket@3.0.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-BInlbhXYrODBPKIplDAmI0J1VPM+1KhCLN09o+dzgQ8qMyrYs4t5kEYmCrTqyRuMTmpahylHFZWQXpfYyDkqOw==}
peerDependencies:
react: '>= 16.8.0'
react-dom: '>= 16.8.0'
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}

View File

@ -7,6 +7,7 @@ import { cn } from "@/common/utils";
import { getMojangEndpointStatus } from "mcutils-library";
import { Metadata } from "next";
import Link from "next/link";
import { ReactElement } from "react";
/**
* Force the page to be dynamic, so it will be regenerated on every request
@ -58,7 +59,7 @@ export async function generateMetadata(): Promise<Metadata> {
});
}
export default async function Page(): Promise<JSX.Element> {
export default async function Page(): Promise<ReactElement> {
const { endpoints } = await getMojangEndpointStatus();
const endpointsSize = Object.entries(endpoints).length;

View File

@ -1,6 +1,7 @@
import Link from "next/link";
import { Button } from "../components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip";
import { ReactElement } from "react";
type Button = {
title: string;
@ -24,7 +25,7 @@ const buttons: Button[] = [
},
];
export default function Home(): JSX.Element {
export default function Home(): ReactElement {
return (
<div className="text-center flex flex-col justify-center">
<h1 className="text-4xl mb-2">Minecraft Utilities</h1>

View File

@ -9,6 +9,7 @@ import { CachedPlayer, McUtilsAPIError, SkinPart, getPlayer } from "mcutils-libr
import { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { ReactElement } from "react";
type Params = {
params: {
@ -46,7 +47,7 @@ export async function generateMetadata({ params: { id } }: Params): Promise<Meta
}
}
export default async function Page({ params: { id } }: Params): Promise<JSX.Element> {
export default async function Page({ params: { id } }: Params): Promise<ReactElement> {
let error: string | undefined = undefined; // The error to display
let player: CachedPlayer | undefined = undefined; // The player to display

View File

@ -13,6 +13,7 @@ import {
} from "mcutils-library";
import { Metadata } from "next";
import Image from "next/image";
import { ReactElement } from "react";
type Params = {
params: {
@ -30,7 +31,7 @@ type Params = {
*/
function getFavicon(
platform: ServerPlatform,
server: CachedJavaMinecraftServer | CachedBedrockMinecraftServer
server: CachedJavaMinecraftServer | CachedBedrockMinecraftServer,
): string | undefined {
if (platform === ServerPlatform.Bedrock) {
return undefined;
@ -88,14 +89,13 @@ export async function generateMetadata({ params: { platform, hostname } }: Param
}
}
export default async function Page({ params: { platform, hostname } }: Params): Promise<JSX.Element> {
export default async function Page({ params: { platform, hostname } }: Params): Promise<ReactElement> {
let error: string | undefined = undefined; // The error to display
let server: CachedJavaMinecraftServer | CachedBedrockMinecraftServer | undefined = undefined; // The server to display
let invalidPlatform = checkPlatform(platform) === false; // Whether the platform is invalid
let invalidPlatform = !checkPlatform(platform); // Whether the platform is invalid
// Try and get the player to display
try {
console.log(platform);
if (invalidPlatform) {
error = "Invalid platform"; // Set the error message
} else {

View File

@ -0,0 +1,14 @@
import { ReactElement } from "react";
import { Stats } from "@/app/components/stats";
export default async function Page(): Promise<ReactElement> {
return (
<div className="flex flex-col gap-4 items-center">
<div className="text-center">
<p className="font-semibold text-xl">API Statistics</p>
<p>View the statistics for the API in real-time!</p>
</div>
<Stats />
</div>
);
}

View File

@ -8,7 +8,7 @@ export default function Container({
return (
<div className="z-[9999] m-auto flex h-screen min-h-full flex-col items-center opacity-90 w-full xs:max-w-[1200px]">
<NavBar />
<div className="w-full flex m-4 justify-center">{children}</div>
<div className="w-full h-full flex m-4 justify-center">{children}</div>
</div>
);
}

View File

@ -3,7 +3,10 @@ import { IconProps } from "./icon-props";
export function MoonIcon({ size = 24, color = "#fff" }: IconProps): JSX.Element {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} fill={color} viewBox="0 -960 960 960">
<path d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z" />
<path
xmlns="http://www.w3.org/2000/svg"
d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q8 0 17 .5t23 1.5q-36 32-56 79t-20 99q0 90 63 153t153 63q52 0 99-18.5t79-51.5q1 12 1.5 19.5t.5 14.5q0 150-105 255T480-120Zm0-60q109 0 190-67.5T771-406q-25 11-53.667 16.5Q688.667-384 660-384q-114.689 0-195.345-80.655Q384-545.311 384-660q0-24 5-51.5t18-62.5q-98 27-162.5 109.5T180-480q0 125 87.5 212.5T480-180Zm-4-297Z"
/>
</svg>
);
}

View File

@ -0,0 +1,12 @@
import { IconProps } from "./icon-props";
export function StorageIcon({ size = 24, color = "#fff" }: IconProps): JSX.Element {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} fill={color} viewBox="0 -960 960 960">
<path
xmlns="http://www.w3.org/2000/svg"
d="M120-160v-148h720v148H120Zm60-38h72v-72h-72v72Zm-60-454v-148h720v148H120Zm60-38h72v-72h-72v72Zm-60 284v-148h720v148H120Zm60-38h72v-72h-72v72Z"
/>
</svg>
);
}

View File

@ -0,0 +1,20 @@
import { ReactElement } from "react";
import CountUp from "react-countup";
type StatProps = {
title: string;
value: number;
icon: ReactElement;
};
export function Stat({ title, value, icon }: StatProps): ReactElement {
return (
<div className="bg-secondary p-2 rounded-lg flex divide-x justify-center items-center text-center w-fit">
<div className="pr-2">{icon}</div>
<div className="pl-2">
<p>{title}</p>
<CountUp start={0} end={value} duration={0.5} preserveValue />
</div>
</div>
);
}

View File

@ -0,0 +1,35 @@
"use client";
import { ReactElement } from "react";
import { StorageIcon } from "@/app/components/icon/storage-icon";
import { Stat } from "@/app/components/stat";
import useWebSocket from "react-use-websocket";
type Stat = {
id: string;
displayName: string;
icon: ReactElement;
};
const stats: Stat[] = [
{
id: "totalRequests",
displayName: "Total Requests",
icon: <StorageIcon />,
},
];
export function Stats(): ReactElement {
const { lastMessage, readyState } = useWebSocket("https://api.mcutils.xyz/websocket/metrics");
const metrics = lastMessage !== null ? JSON.parse(lastMessage.data).metrics : undefined;
return (
<>
{stats.map((stat, index) => {
const value = metrics ? metrics[stat.id] : "???";
return <Stat key={index} title={stat.displayName} value={value} icon={stat.icon} />;
})}
</>
);
}