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 }) {