From d79c6b1b4901fdf6dc5efb7612ece6018e946533 Mon Sep 17 00:00:00 2001 From: cialloo Date: Sat, 4 Oct 2025 22:48:57 +0800 Subject: [PATCH] Refactor server data structure and update localization for server categories and status --- src/locales/en.json | 6 +- src/locales/zh.json | 6 +- src/pages/Servers.tsx | 358 +++++++++++++++++------------------------- 3 files changed, 150 insertions(+), 220 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 7969f7b..7203932 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -86,15 +86,15 @@ "title": "Game Servers", "subtitle": "Join active Counter-Strike servers and compete with players worldwide", "players": "Players", - "ping": "Ping", + "status": "Status", "joinServer": "Join Server", "maintenance": "Maintenance", "offline": "Offline", "serverStats": "Server Statistics", "onlineServers": "Online Servers", "totalPlayers": "Total Players", - "regions": "Regions", - "uptime": "Uptime" + "categories": "Categories", + "totalServers": "Total Servers" }, "forum": { "title": "Community Forum", diff --git a/src/locales/zh.json b/src/locales/zh.json index 1623309..297810b 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -85,15 +85,15 @@ "title": "游戏服务器", "subtitle": "加入活跃的反恐精英服务器,与全球玩家竞技", "players": "玩家", - "ping": "延迟", + "status": "状态", "joinServer": "加入服务器", "maintenance": "维护中", "offline": "离线", "serverStats": "服务器统计", "onlineServers": "在线服务器", "totalPlayers": "总玩家数", - "regions": "地区", - "uptime": "正常运行时间" + "categories": "分类", + "totalServers": "总服务器数" }, "forum": { "title": "社区论坛", diff --git a/src/pages/Servers.tsx b/src/pages/Servers.tsx index 8b2e224..d346e91 100644 --- a/src/pages/Servers.tsx +++ b/src/pages/Servers.tsx @@ -1,184 +1,106 @@ import { useTranslation } from 'react-i18next' +import { useState, useEffect } from 'react' import Layout from '../components/Layout' import '../App.css' -interface Server { - id: number +interface ServerInfo { name: string - map: string - gameMode: string - players: number + ip: string + port: number + category: 'Surf' | 'Kz' | 'Bhop' +} + +interface A2SResponse { + appID: number + botCount: number + environment: string + gameDescription: string + gameDirectory: string + mapName: string maxPlayers: number - ping: number - region: string - difficulty: 'Easy' | 'Normal' | 'Hard' | 'Expert' - status: 'online' | 'offline' | 'maintenance' - description: string - tags: string[] + playerCount: number + serverName: string + serverType: string + vac: number + version: string + visibility: number +} + +interface ServerData extends ServerInfo { + a2sData?: A2SResponse + status: 'online' | 'offline' | 'loading' + error?: string } function Servers() { const { t } = useTranslation() - // Mock server data - const servers: Server[] = [ - { - id: 1, - name: "Dust2 Classic - Competitive", - map: "de_dust2", - gameMode: "Competitive", - players: 8, - maxPlayers: 10, - ping: 23, - region: "Asia-Pacific", - difficulty: "Normal", - status: "online", - description: "Classic Dust2 competitive matches with skilled players", - tags: ["Competitive", "Ranked", "Dust2"] - }, - { - id: 2, - name: "Mirage Pro League", - map: "de_mirage", - gameMode: "Premier", - players: 10, - maxPlayers: 10, - ping: 45, - region: "Europe", - difficulty: "Hard", - status: "online", - description: "Professional Mirage matches for experienced players", - tags: ["Premier", "Mirage", "Pro"] - }, - { - id: 3, - name: "Inferno Casual", - map: "de_inferno", - gameMode: "Casual", - players: 6, - maxPlayers: 12, - ping: 67, - region: "North America", - difficulty: "Easy", - status: "online", - description: "Relaxed Inferno matches for all skill levels", - tags: ["Casual", "Inferno", "Beginner"] - }, - { - id: 4, - name: "Nuke Tactical", - map: "de_nuke", - gameMode: "Tactical", - players: 9, - maxPlayers: 10, - ping: 34, - region: "Asia-Pacific", - difficulty: "Expert", - status: "online", - description: "Tactical Nuke gameplay with strategic objectives", - tags: ["Tactical", "Nuke", "Strategy"] - }, - { - id: 5, - name: "Vertigo Deathmatch", - map: "de_vertigo", - gameMode: "Deathmatch", - players: 12, - maxPlayers: 16, - ping: 28, - region: "Europe", - difficulty: "Normal", - status: "online", - description: "Fast-paced Vertigo deathmatch action", - tags: ["Deathmatch", "Vertigo", "FFA"] - }, - { - id: 6, - name: "Ancient Wingman", - map: "de_ancient", - gameMode: "Wingman", - players: 2, - maxPlayers: 4, - ping: 52, - region: "North America", - difficulty: "Normal", - status: "online", - description: "2v2 Wingman matches on Ancient", - tags: ["Wingman", "Ancient", "2v2"] - }, - { - id: 7, - name: "Overpass Scrim", - map: "de_overpass", - gameMode: "Scrimmage", - players: 8, - maxPlayers: 10, - ping: 41, - region: "South America", - difficulty: "Hard", - status: "online", - description: "Team scrimmage matches for practice", - tags: ["Scrimmage", "Overpass", "Practice"] - }, - { - id: 8, - name: "Cache Community", - map: "de_cache", - gameMode: "Community", - players: 0, - maxPlayers: 10, - ping: 89, - region: "Europe", - difficulty: "Easy", - status: "maintenance", - description: "Community server - currently under maintenance", - tags: ["Community", "Cache", "Social"] - }, - { - id: 9, - name: "Train Pro League", - map: "de_train", - gameMode: "Premier", - players: 10, - maxPlayers: 10, - ping: 31, - region: "Asia-Pacific", - difficulty: "Expert", - status: "online", - description: "Elite Train matches for top players", - tags: ["Premier", "Train", "Elite"] - }, - { - id: 10, - name: "Cobblestone Casual", - map: "de_cbble", - gameMode: "Casual", - players: 4, - maxPlayers: 12, - ping: 76, - region: "North America", - difficulty: "Easy", - status: "online", - description: "Casual gameplay on the classic Cobblestone", - tags: ["Casual", "Cobblestone", "Classic"] - } + // Server list from database export + const serverList: ServerInfo[] = [ + { name: 'Surf 66 Tick #1', ip: '14.103.233.1', port: 27015, category: 'Surf' }, + { name: 'Surf 66 Tick #2', ip: '14.103.233.1', port: 27016, category: 'Surf' }, + { name: 'Surf 66 Tick #3', ip: '14.103.233.1', port: 27017, category: 'Surf' }, + { name: 'Surf 100 Tick #1', ip: '14.103.233.1', port: 28015, category: 'Surf' }, + { name: 'Surf 100 Tick #2', ip: '14.103.233.1', port: 28016, category: 'Surf' }, + { name: 'Kz #1', ip: '14.103.233.1', port: 29015, category: 'Kz' }, + { name: 'Kz #2', ip: '14.103.233.1', port: 29016, category: 'Kz' }, + { name: 'Bhop #1', ip: '14.103.233.1', port: 30015, category: 'Bhop' }, + { name: 'Bhop #2', ip: '14.103.233.1', port: 30016, category: 'Bhop' } ] + const [servers, setServers] = useState([]) + const [selectedCategory, setSelectedCategory] = useState('All') + + // Fetch A2S data for all servers + useEffect(() => { + const fetchServerData = async () => { + const serverPromises = serverList.map(async (server) => { + try { + const response = await fetch('/api/server/statistics/a2s-query', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + serverIP: server.ip, + serverPort: server.port, + timeout: 3000 + }) + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + const a2sData: A2SResponse = await response.json() + return { ...server, a2sData, status: 'online' as const } + } catch (error) { + console.error(`Failed to fetch data for ${server.name}:`, error) + return { + ...server, + status: 'offline' as const, + error: error instanceof Error ? error.message : 'Unknown error' + } + } + }) + + const results = await Promise.all(serverPromises) + setServers(results) + } + + fetchServerData() + }, []) + + // Filter servers by category + const filteredServers = selectedCategory === 'All' + ? servers + : servers.filter(server => server.category === selectedCategory) + + const categories = ['All', ...Array.from(new Set(serverList.map(s => s.category)))] + const getStatusColor = (status: string) => { switch (status) { case 'online': return '#10b981' case 'offline': return '#ef4444' - case 'maintenance': return '#f59e0b' - default: return '#6b7280' - } - } - - const getDifficultyColor = (difficulty: string) => { - switch (difficulty) { - case 'Easy': return '#10b981' - case 'Normal': return '#3b82f6' - case 'Hard': return '#f59e0b' - case 'Expert': return '#ef4444' + case 'loading': return '#f59e0b' default: return '#6b7280' } } @@ -210,6 +132,34 @@ function Servers() { }}> {t('servers.subtitle')}

+ + {/* Category Filter */} +
+ {categories.map(category => ( + + ))} +
@@ -224,9 +174,9 @@ function Servers() { gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))', gap: '2rem' }}> - {servers.map((server) => ( + {filteredServers.map((server) => (
- 🗺️ {server.map} + 🗺️ {server.a2sData?.mapName || 'Loading...'} - {server.gameMode} + {server.category}
@@ -301,7 +251,7 @@ function Servers() { fontWeight: 'bold', color: 'var(--text-primary)' }}> - {server.players}/{server.maxPlayers} + {server.a2sData ? `${server.a2sData.playerCount - server.a2sData.botCount}/${server.a2sData.maxPlayers}` : 'Loading...'}
@@ -310,14 +260,14 @@ function Servers() { color: 'var(--text-secondary)', marginBottom: '0.25rem' }}> - {t('servers.ping')} + {t('servers.status')}
- {server.ping}ms + {server.status === 'online' ? 'Online' : server.status === 'offline' ? 'Offline' : 'Loading'}
@@ -332,45 +282,27 @@ function Servers() { color: 'var(--text-secondary)', marginBottom: '0.5rem' }}> - 🌍 {server.region} + � {server.ip}:{server.port} - {server.difficulty} + {server.category} -

- {server.description} -

- - - {/* Tags */} -
- {server.tags.map((tag, index) => ( - - {tag} - - ))} + {server.error && ( +

+ Error: {server.error} +

+ )}
{/* Join Button */} @@ -394,9 +326,7 @@ function Servers() { } }} > - {server.status === 'online' ? t('servers.joinServer') : - server.status === 'maintenance' ? t('servers.maintenance') : - t('servers.offline')} + {server.status === 'online' ? t('servers.joinServer') : t('servers.offline')} ))} @@ -446,7 +376,7 @@ function Servers() { color: 'var(--accent-primary)', marginBottom: '0.5rem' }}> - {servers.reduce((sum, s) => sum + s.players, 0)} + {servers.reduce((sum, s) => sum + (s.a2sData ? (s.a2sData.playerCount - s.a2sData.botCount) : 0), 0)}
- 4 + {Array.from(new Set(servers.map(s => s.category))).length}
- {t('servers.regions')} + {t('servers.categories')}
@@ -478,13 +408,13 @@ function Servers() { color: 'var(--accent-primary)', marginBottom: '0.5rem' }}> - 95% + {servers.length}
- {t('servers.uptime')} + {t('servers.totalServers')}