From 0c685becf01985c91af221124bde5120bc1cc217 Mon Sep 17 00:00:00 2001 From: Ikiae <64362443+IkiaeM@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:29:28 +0000 Subject: [PATCH 1/2] Add dynamic tracker health checks with real-time status indicators --- app/pages/admin.vue | 10 ++- app/pages/index.vue | 10 ++- server/api/admin/stats.get.ts | 21 ++--- server/api/tracker-status.get.ts | 15 ++++ server/utils/protocolHealthCheck.ts | 123 ++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 server/api/tracker-status.get.ts create mode 100644 server/utils/protocolHealthCheck.ts diff --git a/app/pages/admin.vue b/app/pages/admin.vue index f1b1e7b..49d08f9 100644 --- a/app/pages/admin.vue +++ b/app/pages/admin.vue @@ -34,10 +34,12 @@ > LIVE TRACKER FEED @@ -134,4 +136,10 @@ const currentItem = computed( const currentTitle = computed(() => currentItem?.value?.label); const currentDescription = computed(() => currentItem?.value?.description); + +const { data: trackerStatus } = await useFetch('/api/tracker-status', { + server: false, +}); + +const trackerOnline = computed(() => trackerStatus.value?.online ?? false); diff --git a/app/pages/index.vue b/app/pages/index.vue index 5cb2182..e83df8d 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -7,10 +7,12 @@ > ( ); const recentTorrents = computed(() => torrentsData.value?.data ?? []); + +const { data: trackerStatus } = await useFetch('/api/tracker-status', { + server: false, +}); + +const trackerOnline = computed(() => trackerStatus.value?.online ?? false); diff --git a/server/api/admin/stats.get.ts b/server/api/admin/stats.get.ts index 8e7328e..6b01aed 100644 --- a/server/api/admin/stats.get.ts +++ b/server/api/admin/stats.get.ts @@ -3,6 +3,7 @@ import { db, schema } from '../../db'; import { sql } from 'drizzle-orm'; import { redis } from '../../redis/client'; import { requireAdminSession } from '../../utils/adminAuth'; +import { checkProtocolHealth } from '../../utils/protocolHealthCheck'; export default defineEventHandler(async (event) => { // Require admin authentication @@ -51,23 +52,11 @@ export default defineEventHandler(async (event) => { console.error('[Stats] Failed to fetch peer count from Redis:', err); } - // Try to get tracker, may fail if native modules aren't built - let tracker = null; - let protocols = { http: false, udp: false, ws: false }; + // Check protocol health dynamically + const protocols = await checkProtocolHealth(); - try { - const { getTracker } = await import('../../tracker'); - tracker = getTracker(); - if (tracker) { - protocols = { - http: !!tracker.http, - udp: !!tracker.udp, - ws: !!tracker.ws, - }; - } - } catch { - // Tracker not available (native modules not built) - } + // Determine tracker status based on protocol health + const tracker = protocols.http || protocols.udp || protocols.ws; return { status: tracker ? 'running' : 'stopped', diff --git a/server/api/tracker-status.get.ts b/server/api/tracker-status.get.ts new file mode 100644 index 0000000..f8d6b49 --- /dev/null +++ b/server/api/tracker-status.get.ts @@ -0,0 +1,15 @@ +import { checkProtocolHealth } from '../utils/protocolHealthCheck'; + +/** + * GET /api/tracker-status + * Lightweight endpoint to check if tracker is online + * Used for real-time status indicators on homepage and admin panel + */ +export default defineEventHandler(async () => { + const protocols = await checkProtocolHealth(); + + return { + online: protocols.http || protocols.udp || protocols.ws, + protocols, + }; +}); diff --git a/server/utils/protocolHealthCheck.ts b/server/utils/protocolHealthCheck.ts new file mode 100644 index 0000000..e91b0ce --- /dev/null +++ b/server/utils/protocolHealthCheck.ts @@ -0,0 +1,123 @@ +/** + * Protocol Health Check Utility + * Dynamically checks if tracker protocols are actually responding + */ + +export interface ProtocolStatus { + http: boolean; + udp: boolean; + ws: boolean; +} + +/** + * Check if HTTP tracker is responding + */ +async function checkHttpHealth(): Promise { + try { + // Use local tracker port for health check to avoid Cloudflare/proxy issues + const httpPort = process.env.TRACKER_HTTP_PORT || '8080'; + const httpUrl = `http://localhost:${httpPort}`; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 2000); // 2 second timeout + + try { + const response = await fetch(httpUrl, { + method: 'GET', + signal: controller.signal, + }); + clearTimeout(timeoutId); + + // Read response body + const text = await response.text(); + + // Valid tracker responses are bencoded and start with 'd' + if (text.startsWith('d')) { + return true; + } + + // If we get here, it's not a valid tracker response + return false; + } catch (fetchError: any) { + clearTimeout(timeoutId); + + // Timeout means server didn't respond + if (fetchError.name === 'AbortError') { + return false; + } + + // Connection refused means server is down + if (fetchError.cause?.code === 'ECONNREFUSED' || fetchError.cause?.code === 'ECONNRESET') { + return false; + } + + // Network errors mean server is unreachable + return false; + } + } catch (error) { + console.error('[Health Check] HTTP check failed:', error); + return false; + } +} + +/** + * Check if UDP tracker is responding + * TODO: Implement UDP health check + * Port: 6969 (from TRACKER_UDP_URL) + */ +async function checkUdpHealth(): Promise { + try { + const udpUrl = process.env.TRACKER_UDP_URL; + if (!udpUrl) { + return false; + } + + // TODO: Implement UDP connection test + // This requires sending a UDP connect request to the tracker + // and waiting for a response + // For now, return false as UDP is disabled in the tracker config + return false; + } catch (error) { + console.error('[Health Check] UDP check failed:', error); + return false; + } +} + +/** + * Check if WebSocket tracker is responding + * TODO: Implement WebSocket health check + */ +async function checkWsHealth(): Promise { + try { + const wsUrl = process.env.TRACKER_WS_URL; + if (!wsUrl) { + return false; + } + + // TODO: Implement WebSocket connection test + // This requires opening a WebSocket connection and checking if it connects + // For now, return false as WS is disabled in the tracker config + return false; + } catch (error) { + console.error('[Health Check] WebSocket check failed:', error); + return false; + } +} + +/** + * Check all protocol health statuses + * Returns the current status of each protocol + */ +export async function checkProtocolHealth(): Promise { + const [http, udp, ws] = await Promise.all([ + checkHttpHealth(), + checkUdpHealth(), + checkWsHealth(), + ]); + + return { + http, + udp, + ws, + }; +} From bf30a15955b6459e1cf341d20d13a52729f62db0 Mon Sep 17 00:00:00 2001 From: Ikiae <64362443+IkiaeM@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:13:35 +0000 Subject: [PATCH 2/2] Add separate status badge text for online and offline tracker states - Split statusBadgeText into statusBadgeTextOnline and statusBadgeTextOffline settings - Add offline status badge input field in admin homepage content editor - Update homepage to display appropriate status badge based on tracker health - Add getStatusBadgeTextOffline utility function with default fallback text - Update all API endpoints and settings keys to handle both online/offline badge texts --- app/components/admin/HomepageContent.vue | 31 +++++++++++++++++++----- app/pages/index.vue | 2 +- server/api/admin/settings.get.ts | 9 ++++--- server/api/admin/settings.put.ts | 8 ++++-- server/api/homepage-content.get.ts | 9 ++++--- server/utils/schemas.ts | 3 ++- server/utils/settings.ts | 17 ++++++++++--- 7 files changed, 59 insertions(+), 20 deletions(-) diff --git a/app/components/admin/HomepageContent.vue b/app/components/admin/HomepageContent.vue index 97c40d7..6c2d371 100644 --- a/app/components/admin/HomepageContent.vue +++ b/app/components/admin/HomepageContent.vue @@ -51,16 +51,31 @@ + +
+ + +
@@ -108,7 +123,8 @@