From defdcccdb6bea3e098d28ee16c9c59885b092f28 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 18 Apr 2024 01:21:38 +0100 Subject: [PATCH] add realtime statistics!!! --- .fleet/settings.json | 4 +++ .prettierrc | 12 +++++++ package.json | 2 ++ pnpm-lock.yaml | 29 +++++++++++++++ src/app/(pages)/mojang/status/page.tsx | 3 +- src/app/(pages)/page.tsx | 3 +- src/app/(pages)/player/[[...id]]/page.tsx | 3 +- .../[platform]/[[...hostname]]/page.tsx | 8 ++--- src/app/(pages)/stats/page.tsx | 14 ++++++++ src/app/components/container.tsx | 2 +- src/app/components/icon/moon-icon.tsx | 5 ++- src/app/components/icon/storage-icon.tsx | 12 +++++++ src/app/components/stat.tsx | 20 +++++++++++ src/app/components/stats.tsx | 35 +++++++++++++++++++ 14 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 .fleet/settings.json create mode 100644 .prettierrc create mode 100644 src/app/(pages)/stats/page.tsx create mode 100644 src/app/components/icon/storage-icon.tsx create mode 100644 src/app/components/stat.tsx create mode 100644 src/app/components/stats.tsx diff --git a/.fleet/settings.json b/.fleet/settings.json new file mode 100644 index 0000000..3641d1c --- /dev/null +++ b/.fleet/settings.json @@ -0,0 +1,4 @@ +{ + "toolchains": [], + "nodejs.editor.formatOnSave.prettier.mode": "Enabled" +} \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..56d9b8b --- /dev/null +++ b/.prettierrc @@ -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 +} \ No newline at end of file diff --git a/package.json b/package.json index 0dabdec..d6ae1f3 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a681aea..e9dba7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/src/app/(pages)/mojang/status/page.tsx b/src/app/(pages)/mojang/status/page.tsx index 8604bb7..08ac819 100644 --- a/src/app/(pages)/mojang/status/page.tsx +++ b/src/app/(pages)/mojang/status/page.tsx @@ -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 { }); } -export default async function Page(): Promise { +export default async function Page(): Promise { const { endpoints } = await getMojangEndpointStatus(); const endpointsSize = Object.entries(endpoints).length; diff --git a/src/app/(pages)/page.tsx b/src/app/(pages)/page.tsx index 61dde20..f0a0318 100644 --- a/src/app/(pages)/page.tsx +++ b/src/app/(pages)/page.tsx @@ -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 (

Minecraft Utilities

diff --git a/src/app/(pages)/player/[[...id]]/page.tsx b/src/app/(pages)/player/[[...id]]/page.tsx index bd168e8..01ae7d1 100644 --- a/src/app/(pages)/player/[[...id]]/page.tsx +++ b/src/app/(pages)/player/[[...id]]/page.tsx @@ -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 { +export default async function Page({ params: { id } }: Params): Promise { let error: string | undefined = undefined; // The error to display let player: CachedPlayer | undefined = undefined; // The player to display diff --git a/src/app/(pages)/server/[platform]/[[...hostname]]/page.tsx b/src/app/(pages)/server/[platform]/[[...hostname]]/page.tsx index aebcac1..79bd609 100644 --- a/src/app/(pages)/server/[platform]/[[...hostname]]/page.tsx +++ b/src/app/(pages)/server/[platform]/[[...hostname]]/page.tsx @@ -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 { +export default async function Page({ params: { platform, hostname } }: Params): Promise { 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 { diff --git a/src/app/(pages)/stats/page.tsx b/src/app/(pages)/stats/page.tsx new file mode 100644 index 0000000..55332af --- /dev/null +++ b/src/app/(pages)/stats/page.tsx @@ -0,0 +1,14 @@ +import { ReactElement } from "react"; +import { Stats } from "@/app/components/stats"; + +export default async function Page(): Promise { + return ( +
+
+

API Statistics

+

View the statistics for the API in real-time!

+
+ +
+ ); +} diff --git a/src/app/components/container.tsx b/src/app/components/container.tsx index 99b5b6a..1256bb9 100644 --- a/src/app/components/container.tsx +++ b/src/app/components/container.tsx @@ -8,7 +8,7 @@ export default function Container({ return (
-
{children}
+
{children}
); } diff --git a/src/app/components/icon/moon-icon.tsx b/src/app/components/icon/moon-icon.tsx index 9b34a30..d8ec17b 100644 --- a/src/app/components/icon/moon-icon.tsx +++ b/src/app/components/icon/moon-icon.tsx @@ -3,7 +3,10 @@ import { IconProps } from "./icon-props"; export function MoonIcon({ size = 24, color = "#fff" }: IconProps): JSX.Element { return ( - + ); } diff --git a/src/app/components/icon/storage-icon.tsx b/src/app/components/icon/storage-icon.tsx new file mode 100644 index 0000000..368ac15 --- /dev/null +++ b/src/app/components/icon/storage-icon.tsx @@ -0,0 +1,12 @@ +import { IconProps } from "./icon-props"; + +export function StorageIcon({ size = 24, color = "#fff" }: IconProps): JSX.Element { + return ( + + + + ); +} diff --git a/src/app/components/stat.tsx b/src/app/components/stat.tsx new file mode 100644 index 0000000..fdb8433 --- /dev/null +++ b/src/app/components/stat.tsx @@ -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 ( +
+
{icon}
+
+

{title}

+ +
+
+ ); +} diff --git a/src/app/components/stats.tsx b/src/app/components/stats.tsx new file mode 100644 index 0000000..d917465 --- /dev/null +++ b/src/app/components/stats.tsx @@ -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: , + }, +]; + +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 ; + })} + + ); +}