add realtime statistics!!!
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
This commit is contained in:
parent
f5203f9742
commit
defdcccdb6
4
.fleet/settings.json
Normal file
4
.fleet/settings.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"toolchains": [],
|
||||
"nodejs.editor.formatOnSave.prettier.mode": "Enabled"
|
||||
}
|
12
.prettierrc
Normal file
12
.prettierrc
Normal 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
|
||||
}
|
@ -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
29
pnpm-lock.yaml
generated
@ -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'}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
14
src/app/(pages)/stats/page.tsx
Normal file
14
src/app/(pages)/stats/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
12
src/app/components/icon/storage-icon.tsx
Normal file
12
src/app/components/icon/storage-icon.tsx
Normal 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>
|
||||
);
|
||||
}
|
20
src/app/components/stat.tsx
Normal file
20
src/app/components/stat.tsx
Normal 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>
|
||||
);
|
||||
}
|
35
src/app/components/stats.tsx
Normal file
35
src/app/components/stats.tsx
Normal 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} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user