Performance: RCON pooling, route caching, parallel status probe
Server: - lib/cache.ts: tiny TTL memo for hot paths. - lib/rcon.ts: pooled connection with 30s idle close and one-shot retry; was opening a fresh TCP per RCON call. - /api/status: race protocol-ping + RCON with Promise.any (was sequential with 5s timeouts) and memo 3s; adds Cache-Control. - /api/players: memo 10s, invalidated on mutations. - /api/mods: getModDetails memo 10s (invalidated on install/remove) — eliminates per-request readdirSync/statSync/AdmZip parse. - /api/analytics: reverse-scan JSONL to window cutoff (O(window) vs O(file)) and memo 30s. - /api/logs: memo 2s to throttle journalctl spawns during Live mode. Client: - StatusCard + ServerControls: staleTime 5s, both poll every 10s, dedup via shared query key. - Analytics: staleTime 30s; memoize sparkline SVG paths. - Modrinth search icons + mc-heads avatars: width/height + loading=lazy + decoding=async. - next.config.ts: images.remotePatterns for future next/image migration, explicit compress=true. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b6b10159ad
commit
b6cf8c7cdc
14 changed files with 220 additions and 82 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -55,16 +55,17 @@ function Sparkline({
|
|||
);
|
||||
}
|
||||
|
||||
const dataMax = max || Math.max(...data, 1);
|
||||
const w = 300;
|
||||
const points = data.map((v, i) => {
|
||||
const x = (i / (data.length - 1)) * w;
|
||||
const y = height - (v / dataMax) * (height - 10) - 5;
|
||||
return `${x},${y}`;
|
||||
});
|
||||
|
||||
const pathD = `M${points.join(" L")}`;
|
||||
const areaD = `${pathD} L${w},${height} L0,${height} Z`;
|
||||
const { pathD, areaD, w } = useMemo(() => {
|
||||
const dataMax = max || Math.max(...data, 1);
|
||||
const width = 300;
|
||||
const pts = data.map((v, i) => {
|
||||
const x = (i / (data.length - 1)) * width;
|
||||
const y = height - (v / dataMax) * (height - 10) - 5;
|
||||
return `${x},${y}`;
|
||||
});
|
||||
const p = `M${pts.join(" L")}`;
|
||||
return { pathD: p, areaD: `${p} L${width},${height} L0,${height} Z`, w: width };
|
||||
}, [data, max, height]);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-muted p-4">
|
||||
|
|
@ -99,6 +100,7 @@ export function Analytics() {
|
|||
queryFn: () =>
|
||||
fetch(`/api/analytics?hours=${hours}`).then((r) => r.json()),
|
||||
refetchInterval: 60_000,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
|
||||
const latest = metrics.length > 0 ? metrics[metrics.length - 1] : null;
|
||||
|
|
|
|||
|
|
@ -489,7 +489,11 @@ export function ModManager() {
|
|||
<img
|
||||
src={result.icon_url}
|
||||
alt=""
|
||||
className="w-10 h-10 rounded-md shrink-0"
|
||||
width={40}
|
||||
height={40}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
className="w-10 h-10 rounded-md shrink-0 bg-muted"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-md bg-muted shrink-0" />
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ export function PlayerAvatar({
|
|||
alt=""
|
||||
width={size}
|
||||
height={size}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
onError={() => setFailed(true)}
|
||||
className={`rounded shrink-0 bg-muted ${className}`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export function ServerControls() {
|
|||
queryKey: ["status"],
|
||||
queryFn: () => fetch("/api/status").then((r) => r.json()),
|
||||
refetchInterval: 10000,
|
||||
staleTime: 5000,
|
||||
});
|
||||
|
||||
const action = useMutation({
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ export function StatusCard() {
|
|||
const { data, isLoading } = useQuery<ServerStatus>({
|
||||
queryKey: ["status"],
|
||||
queryFn: () => fetch("/api/status").then((r) => r.json()),
|
||||
refetchInterval: 15000,
|
||||
refetchInterval: 10000,
|
||||
staleTime: 5000,
|
||||
});
|
||||
|
||||
const copyIP = () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue