diff --git a/app/nodes/page.tsx b/app/nodes/page.tsx index a24114c..bf7c396 100644 --- a/app/nodes/page.tsx +++ b/app/nodes/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next" import { NodesClient } from "@/packages/ui/components/Layouts/Nodes/nodes-client" +import { fetchStatusSnapshot, getNodeMonitorNames } from "@/packages/core/lib/status" export const metadata: Metadata = { title: "Our Network & Nodes", @@ -7,6 +8,8 @@ export const metadata: Metadata = { "Live status and information about NodeByte Hosting's network. See where our infrastructure is located and what powers our game server and VPS hosting.", } -export default function NodesPage() { - return +export default async function NodesPage() { + const snapshot = await fetchStatusSnapshot() + const nodeNames = getNodeMonitorNames(snapshot) + return } diff --git a/packages/core/constants/node-types.ts b/packages/core/constants/node-types.ts index 7b52be5..1307482 100644 --- a/packages/core/constants/node-types.ts +++ b/packages/core/constants/node-types.ts @@ -3,10 +3,10 @@ export interface PublicNode { name: string locationCode: string isMaintenanceMode: boolean - /** Allocated memory in MiB */ - memory: number - /** Allocated disk in MiB */ - disk: number + /** Allocated memory in MiB — not known for nodes without a display override. */ + memory?: number + /** Allocated disk in MiB — not known for nodes without a display override. */ + disk?: number /** Number of server instances currently provisioned on this node */ serverCount?: number } diff --git a/packages/core/constants/partners.ts b/packages/core/constants/partners.ts index 4a6507b..a46634b 100644 --- a/packages/core/constants/partners.ts +++ b/packages/core/constants/partners.ts @@ -1,21 +1,26 @@ /** - * Partners & sponsors shown on /partners. + * Partners, sponsors, and communities shown on /partners. * - * Add or remove entries here — the page groups them by `tier` automatically - * and shows an empty-state invite for any tier with zero entries, so this - * file can be edited freely without touching the page component. + * Add or remove entries here — the page groups them by `tier` into their own + * section automatically, hiding any tier with zero entries, so this file can + * be edited freely without touching the page component. * - * There's no real partner/sponsor data yet — the entries below are clearly - * marked placeholders. Replace them (or delete down to an empty array) once - * real partners sign on. + * - "sponsor" / "partner": companies and projects we have a business + * relationship with. + * - "community": servers/communities we host or sponsor for free or at a + * discount (Minecraft servers, Discord communities, etc). Set `category` + * to a short label like "Minecraft Server" or "Discord Community" — it + * renders as a badge on the card. */ -export type PartnerTier = "sponsor" | "partner" +export type PartnerTier = "sponsor" | "partner" | "community" export interface PartnerEntry { id: string name: string tier: PartnerTier + /** Short badge label, e.g. "Minecraft Server", "Discord Community". Optional — mainly useful for the community tier. */ + category?: string /** Path under /public, e.g. "/partners/example.svg" */ logo: string url: string @@ -23,28 +28,62 @@ export interface PartnerEntry { } export const PARTNERS: PartnerEntry[] = [ + { + id: "poliberry", + name: "Poliberry", + tier: "partner", + logo: "/partners/poliberry.png", + url: "https://poliberry.com", + description: "Poliberry is a technology company building tools and services to better connect people online and empowering developers to build the next big thing.", + }, { id: "embrly", name: "Emberly", tier: "partner", - logo: "https://embrly.ca/icon.svg", + logo: "/partners/emberly.svg", url: "https://embrly.ca", description: "The open-source platform for secure file sharing and team collaboration. Upload, manage, and share content with custom domains, rich embeds, and built-in talent discovery.", }, + { + id: "clovrme", + name: "Clover", + tier: "partner", + logo: "/partners/clovrme.svg", + url: "https://clovr.me", + description: "A profile page that's fully yours custom themes, music, animated backgrounds, and all your links in one place.", + }, { id: "octo", name: "Octoflow", tier: "partner", - logo: "https://octoflow.ca/logo.png", + logo: "/partners/octoflow.png", url: "https://octoflow.ca", description: "Keep your team connected to every commit, pull request, and deployment without ever leaving your Discord server.", }, { - id: "poliberry", - name: "Poliberry", + id: "antiraid", + name: "AntiRaid", tier: "partner", - logo: "/partners/Frame_1552.png", - url: "https://poliberry.com", - description: "Poliberry is a technology company building tools and services to better connect people online and empowering developers to build the next big thing.", + logo: "/partners/antiraid.webp", + url: "https://antiraid.xyz", + description: "From basic moderation to advanced threat protection, AntiRaid handles it all so you can focus on growing your community.", + }, + { + id: "smphub", + name: "SMP Hub", + tier: "community", + category: "Minecraft Community", + logo: "/partners/smphub.webp", + url: "https://discord.gg/d6sXpA7gXJ", + description: "The premier directory designed to connect the Minecraft community.", + }, + { + id: "blizzardsmp", + name: "Blizzard SMP", + tier: "community", + category: "Minecraft Community", + logo: "/partners/blizzardsmp.webp", + url: "https://discord.gg/mvQ9VqZ4D", + description: "A warm, active community with a cool name and even cooler players.", }, ] diff --git a/packages/core/constants/status-mapping.ts b/packages/core/constants/status-mapping.ts index 7e6418f..06318bf 100644 --- a/packages/core/constants/status-mapping.ts +++ b/packages/core/constants/status-mapping.ts @@ -1,23 +1,27 @@ /** - * Bridges the two independent naming schemes between status.nodebyte.host - * (monitor names) and this site's node/location identifiers. Update these - * maps whenever a node is renamed or a new Region ping monitor is added - * upstream — unmapped entries simply render without live data. + * The node list on /nodes is discovered live from status.nodebyte.host's + * "Nodes" group (see getNodeMonitorNames in lib/status.ts) — adding a node + * there is all that's needed for it to appear on the site. + * + * status.nodebyte.host doesn't carry location/hardware details, so those are + * filled in here as optional per-node overrides, keyed by the exact monitor + * name. A node with no entry here still shows up, just without these extras. */ - -/** Website node `name` (STATIC_NODES) → status.nodebyte.host monitor `name`. */ -export const NODE_MONITOR_MAP: Record = { - "NEWC-GAME1": "NEWC-GAME1", - "NEWY-GAME1": "NEWY-GAME1", - "FLUXRP-FIVEM": "FLUXRP-FIVEM", - "HEL-VPS1": "HEL-VPS1", +export const NODE_DISPLAY_OVERRIDES: Record = { + "NEWC-GAME1": { locationCode: "Newcastle, UK" }, + "NEWY-GAME1": { locationCode: "New York, USA" }, + "HEL-VPS1": { locationCode: "Helsinki, FI" }, + // Inferred from the "FSN" prefix (Falkenstein) — confirm/correct if wrong. + "FSN-VPS1": { locationCode: "Falkenstein, DE" }, } /** * Website location `id` (LOCATIONS) → status.nodebyte.host "Regions" monitor * `name`. Only locations with a confirmed matching ping monitor are listed; * the Regions group also includes PoPs (e.g. Ashburn VA, Atlanta GA) that - * don't correspond to an actual NodeByte data centre location. + * don't correspond to an actual NodeByte data centre location. Update + * whenever a new Region ping monitor is added upstream — unmapped locations + * simply render without live data. */ export const LOCATION_MONITOR_MAP: Record = { lon: "London, UK", diff --git a/packages/core/lib/status.ts b/packages/core/lib/status.ts index bea5ca8..ed2016b 100644 --- a/packages/core/lib/status.ts +++ b/packages/core/lib/status.ts @@ -108,6 +108,21 @@ export function findMonitor(snapshot: StatusSnapshot | null, name: string): Stat return snapshot.monitors.find((m) => m.name.trim().toLowerCase() === target) ?? null } +/** + * Names of individual node monitors under the "Nodes" status group — this is + * the live source of truth for which nodes exist on /nodes. Excludes the + * "group"-type aggregate rollups (e.g. "Game Servers", "VPS Servers") that + * summarise the individual node monitors rather than representing one. + * Add a node on status.nodebyte.host under the "Nodes" group and it appears + * here automatically — no website code change needed. + */ +export function getNodeMonitorNames(snapshot: StatusSnapshot | null): string[] { + if (!snapshot) return [] + return snapshot.monitors + .filter((m) => m.group_name === "Nodes" && m.type !== "group") + .map((m) => m.name) +} + /** Compute fast/avg/slow latency (ms) from a monitor's recent heartbeats. */ export function computeLatencyStats(monitor: StatusMonitor): { fast: number; avg: number; slow: number } | null { const samples = monitor.heartbeats diff --git a/packages/ui/components/Layouts/Nodes/nodes-client.tsx b/packages/ui/components/Layouts/Nodes/nodes-client.tsx index e7d8ecf..0771fb6 100644 --- a/packages/ui/components/Layouts/Nodes/nodes-client.tsx +++ b/packages/ui/components/Layouts/Nodes/nodes-client.tsx @@ -23,7 +23,7 @@ import { } from "@/packages/ui/components/ui/accordion" import { Alert, AlertDescription } from "@/packages/ui/components/ui/alert" import type { PublicNode } from "@/packages/core/constants/node-types" -import { NODE_MONITOR_MAP, LOCATION_MONITOR_MAP } from "@/packages/core/constants/status-mapping" +import { NODE_DISPLAY_OVERRIDES, LOCATION_MONITOR_MAP } from "@/packages/core/constants/status-mapping" import { useNodeStatus } from "@/packages/core/hooks/use-node-status" import type { StatusApiMonitor } from "@/app/api/status/route" import { cn } from "@/lib/utils" @@ -36,35 +36,20 @@ interface ExtendedNode extends PublicNode { uptime?: number } -const STATIC_NODES: ExtendedNode[] = [ - { - id: 1, - name: "NEWC-GAME1", - locationCode: "Newcastle, UK", - isMaintenanceMode: false, - memory: 1310089, - disk: 1811000, - uptime: 99.9, - }, - { - id: 2, - name: "NEWY-GAME1", - locationCode: "New York, USA", - isMaintenanceMode: false, - memory: 65104, - disk: 512000, - uptime: 99.8, - }, - { - id: 3, - name: "HEL-VPS1", - locationCode: "Helsinki, FI", - isMaintenanceMode: false, - memory: 65104, - disk: 512000, - uptime: 99.8, - }, -] +/** Build the node list from names discovered live from the status page, filling in any known display extras. */ +function buildNodes(nodeNames: string[]): ExtendedNode[] { + return nodeNames.map((name, i) => { + const overrides = NODE_DISPLAY_OVERRIDES[name] ?? {} + return { + id: i + 1, + name, + locationCode: overrides.locationCode ?? "", + isMaintenanceMode: false, + cpu: overrides.cpu, + ramType: overrides.ramType, + } + }) +} // ─── Location data ─────────────────────────────────────────────────────────── @@ -314,13 +299,15 @@ const FAQS = [ // ─── Main export ───────────────────────────────────────────────────────────── -export function NodesClient() { - const nodes = STATIC_NODES +interface NodesClientProps { + /** Node monitor names discovered live from the status page's "Nodes" group. */ + nodeNames: string[] +} + +export function NodesClient({ nodeNames }: NodesClientProps) { + const nodes = buildNodes(nodeNames) const { findMonitor } = useNodeStatus() - const nodeLiveStates = nodes.map((node) => { - const monitorName = NODE_MONITOR_MAP[node.name] - return monitorName ? findMonitor(monitorName) : null - }) + const nodeLiveStates = nodes.map((node) => findMonitor(node.name)) const onlineCount = nodeLiveStates.filter( (live, i) => (live ? live.status === "up" : !nodes[i].isMaintenanceMode), ).length @@ -381,11 +368,19 @@ export function NodesClient() {

-
- {nodes.map((node, i) => ( - - ))} -
+ {nodes.length > 0 ? ( +
+ {nodes.map((node, i) => ( + + ))} +
+ ) : ( +
+ +

Node status is temporarily unavailable

+

Check the status page directly for the latest info.

+
+ )} {/* ── Locations */} diff --git a/packages/ui/components/Layouts/Partners/partners-page.tsx b/packages/ui/components/Layouts/Partners/partners-page.tsx index e155249..7abfa12 100644 --- a/packages/ui/components/Layouts/Partners/partners-page.tsx +++ b/packages/ui/components/Layouts/Partners/partners-page.tsx @@ -2,7 +2,8 @@ import { Card } from "@/packages/ui/components/ui/card" import { Button } from "@/packages/ui/components/ui/button" -import { Handshake, Award, ArrowRight, ExternalLink, MessageCircle, Sparkles } from "lucide-react" +import { Badge } from "@/packages/ui/components/ui/badge" +import { Handshake, Award, Users, ArrowRight, ExternalLink, MessageCircle } from "lucide-react" import Image from "next/image" import Link from "next/link" import { cn } from "@/lib/utils" @@ -32,6 +33,11 @@ function PartnerCard({ entry }: { entry: PartnerEntry }) {

{entry.name}

+ {entry.category && ( + + {entry.category} + + )}

{entry.description}

@@ -40,39 +46,21 @@ function PartnerCard({ entry }: { entry: PartnerEntry }) { ) } -function EmptyTierCard({ label, applyHref }: { label: string; applyHref: string }) { - return ( - - -

- No {label} yet — want to be the first? -

- -
- ) -} - function TierSection({ tier, title, badge, icon: Icon, entries, - emptyLabel, }: { tier: PartnerTier title: string badge: string icon: typeof Handshake entries: PartnerEntry[] - emptyLabel: string }) { const filtered = entries.filter((e) => e.tier === tier) + if (filtered.length === 0) return null return (
@@ -85,17 +73,11 @@ function TierSection({

{title}

- {filtered.length > 0 ? ( -
- {filtered.map((entry) => ( - - ))} -
- ) : ( -
- -
- )} +
+ {filtered.map((entry) => ( + + ))} +
) @@ -151,7 +133,6 @@ export function PartnersPage() { badge={t("partnersPage.sponsors.badge")} icon={Award} entries={PARTNERS} - emptyLabel={t("partnersPage.sponsors.emptyLabel")} /> + + {/* CTA */} diff --git a/public/partners/antiraid.webp b/public/partners/antiraid.webp new file mode 100644 index 0000000..b2ca69b Binary files /dev/null and b/public/partners/antiraid.webp differ diff --git a/public/partners/blizzardsmp.webp b/public/partners/blizzardsmp.webp new file mode 100644 index 0000000..feb4fd5 Binary files /dev/null and b/public/partners/blizzardsmp.webp differ diff --git a/public/partners/clovrme.svg b/public/partners/clovrme.svg new file mode 100644 index 0000000..38e9d4f --- /dev/null +++ b/public/partners/clovrme.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/partners/emberly.svg b/public/partners/emberly.svg new file mode 100644 index 0000000..27ade03 --- /dev/null +++ b/public/partners/emberly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/partners/octoflow.png b/public/partners/octoflow.png new file mode 100644 index 0000000..8069e49 Binary files /dev/null and b/public/partners/octoflow.png differ diff --git a/public/partners/Frame_1552.png b/public/partners/poliberry.png similarity index 100% rename from public/partners/Frame_1552.png rename to public/partners/poliberry.png diff --git a/public/partners/smphub.webp b/public/partners/smphub.webp new file mode 100644 index 0000000..e0224a4 Binary files /dev/null and b/public/partners/smphub.webp differ diff --git a/translations b/translations index 903a53a..d24273c 160000 --- a/translations +++ b/translations @@ -1 +1 @@ -Subproject commit 903a53a07c182c8c2ea32aa7d7b386d4340da7d2 +Subproject commit d24273c4b3fb45b747a249f6f4f8a2503cf2fff6