drop/server/components/ServerStatusPanel.vue
wdunn001 0348d10602 community: M4 -- game-server registry and issue reports
GameServer/GameServerStatusHistory/Issue/IssueComment models, a
strategy-per-probe_type ServerProbe (tcp_connect/http_get/presence_inferred,
mirroring script-library's game-server-registry-probe.py including the
integer ConnectTimeout fix), the bundled verified registry seed (WoW, EQEmu,
the Goldberg relay, Impostor, OpenDAoC), and full player+admin UI at
/community/servers and /community/issues. Featured flag pins the
persistent-world servers (WoW/EQEmu/OpenDAoC) to a compact status panel
surfaced on the community landing page. Scheduled sweep respects a
circuit-breaker backoff and never probes on page load; the relay is never
network-probed, only liveness-checked over ssh.
2026-08-03 03:19:54 -04:00

109 lines
3.1 KiB
Vue

<template>
<div class="rounded-lg bg-zinc-800/50 p-4 flex flex-col gap-y-3">
<div class="flex items-center justify-between">
<h2 class="text-sm font-semibold font-display text-zinc-100 uppercase">
{{ $t("community.servers.panelTitle") }}
</h2>
<NuxtLink
to="/community/servers"
class="text-xs text-zinc-500 hover:text-zinc-300 duration-200"
>
{{ $t("community.servers.viewAll") }}
</NuxtLink>
</div>
<div v-if="loading" class="text-sm text-zinc-500">
{{ $t("common.srLoading") }}
</div>
<div v-else-if="servers.length === 0" class="text-sm text-zinc-500">
{{ $t("community.servers.noneFeatured") }}
</div>
<ul v-else class="flex flex-col divide-y divide-zinc-700/50">
<li
v-for="server in servers"
:key="server.id"
class="py-2.5 first:pt-0 last:pb-0 flex items-start gap-x-3"
>
<span
class="mt-1 size-2.5 rounded-full shrink-0"
:class="statusDotClass(server.status)"
:title="server.status"
/>
<div class="flex flex-col gap-y-0.5 min-w-0 flex-1">
<div class="flex items-center gap-x-2">
<NuxtLink
:to="`/community/servers/${server.id}`"
class="text-sm font-semibold font-display text-zinc-100 hover:text-white truncate"
>
{{ server.name }}
</NuxtLink>
<span class="text-xs text-zinc-500 shrink-0">{{
statusLabel(server.status)
}}</span>
</div>
<div class="text-xs text-zinc-400 flex items-center gap-x-2">
<span>{{ server.host }}:{{ server.port }}</span>
<span v-if="server.playersOnline !== null" class="text-zinc-300">
{{
$t("community.servers.playersOnline", [server.playersOnline])
}}
</span>
<span
v-else-if="server.status === 'up'"
class="text-zinc-500 italic"
>
{{ $t("community.servers.playersUnavailable") }}
</span>
</div>
</div>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
interface PanelServer {
id: string;
name: string;
host: string;
port: number;
status: "up" | "down" | "degraded" | "unknown";
playersOnline: number | null;
}
const loading = ref(true);
const servers = ref<PanelServer[]>([]);
const statusLabel = useServerStatusLabel();
function statusDotClass(status: PanelServer["status"]) {
switch (status) {
case "up":
return "bg-emerald-500";
case "down":
return "bg-red-500";
case "degraded":
return "bg-amber-500";
default:
return "bg-zinc-500";
}
}
async function load() {
loading.value = true;
try {
servers.value = await $dropFetch<PanelServer[]>(
"/api/v1/community/servers",
{
params: { featured: "true" },
},
);
} catch (e) {
console.error("[community] failed to load featured servers", e);
servers.value = [];
} finally {
loading.value = false;
}
}
await load();
</script>