|
1 | 1 | <template> |
2 | 2 | <main> |
3 | 3 | <div class="flex flex-col items-center justify-center"> |
4 | | - <div class="w-full max-w-6xl mx-auto px-4 py-8"> |
5 | | - <h1 class="text-white text-2xl text-center mb-8">Servers</h1> |
6 | | - |
| 4 | + <div class="w-full max-w-5xl mx-auto px-4 py-8"> |
| 5 | + <div class="mb-7"> |
| 6 | + <h1 class="text-white text-2xl text-center mb-2">Servers</h1> |
| 7 | + <p class="text-sm text-gray-400 mb-4 text-center">Active servers that have submitted times within the last 2 weeks</p> |
| 8 | + </div> |
7 | 9 | <!-- Server Statistics --> |
8 | | - <div v-if="!loading && !error && servers.length > 0" class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> |
| 10 | + <div v-if="!loading && !error && servers.length > 0" class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-5"> |
9 | 11 | <div class="bg-main-700 rounded-md p-4"> |
10 | 12 | <div class="flex items-center"> |
11 | | - <div class="p-1.5 bg-main-500 rounded-md"> |
| 13 | + <div class="p-1.5 bg-main-400 rounded-md"> |
12 | 14 | <svg class="w-5 h-5 text-gray-100" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
13 | 15 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2"/> |
14 | 16 | </svg> |
15 | 17 | </div> |
16 | 18 | <div class="ml-3"> |
17 | | - <h3 class="text-xs font-medium text-gray-300">Total Servers</h3> |
18 | | - <p class="text-xl font-semibold text-gray-100">{{ totalServers }}</p> |
| 19 | + <h3 class="text-xs text-gray-400">Total Servers</h3> |
| 20 | + <p class="text-2xl font-mono font-medium text-gray-100">{{ totalServers }}</p> |
19 | 21 | </div> |
20 | 22 | </div> |
21 | 23 | </div> |
22 | 24 |
|
23 | 25 | <div class="bg-main-700 rounded-md p-4"> |
24 | 26 | <div class="flex items-center"> |
25 | | - <div class="p-1.5 bg-green-900 rounded-md"> |
26 | | - <div class="w-5 h-5 bg-green-500 rounded-full"></div> |
| 27 | + <div class="p-1.5 bg-green-900/50 rounded-md"> |
| 28 | + <div class="w-5 h-5 bg-green-500/80 rounded-full"></div> |
27 | 29 | </div> |
28 | 30 | <div class="ml-3"> |
29 | | - <h3 class="text-xs font-medium text-gray-300">Active Servers</h3> |
30 | | - <p class="text-xl font-semibold text-green-400">{{ activeServers.length }}</p> |
| 31 | + <h3 class="text-xs text-gray-400">Active Servers</h3> |
| 32 | + <p class="text-2xl font-mono font-medium text-green-400">{{ activeServers.length }}</p> |
31 | 33 | </div> |
32 | 34 | </div> |
33 | 35 | </div> |
34 | 36 |
|
35 | 37 | <div class="bg-main-700 rounded-md p-4"> |
36 | 38 | <div class="flex items-center"> |
37 | | - <div class="p-1.5 bg-red-900 rounded-md"> |
38 | | - <div class="w-5 h-5 bg-red-500 rounded-full"></div> |
| 39 | + <div class="p-1.5 bg-red-900/50 rounded-md"> |
| 40 | + <div class="w-5 h-5 bg-red-500/80 rounded-full"></div> |
39 | 41 | </div> |
40 | 42 | <div class="ml-3"> |
41 | | - <h3 class="text-xs font-medium text-gray-300">Inactive Servers</h3> |
42 | | - <p class="text-xl font-semibold text-red-400">{{ inactiveServers.length }}</p> |
| 43 | + <h3 class="text-xs text-gray-400">Inactive Servers</h3> |
| 44 | + <p class="text-2xl font-mono font-medium text-red-400">{{ inactiveServers.length }}</p> |
43 | 45 | </div> |
44 | 46 | </div> |
45 | 47 | </div> |
46 | 48 | </div> |
47 | 49 |
|
48 | 50 | <div class="bg-main-700 rounded-md overflow-hidden"> |
49 | | - <div class="px-6 py-4 bg-main-900"> |
| 51 | + <div class="px-5 pr-4 py-4 bg-main-900"> |
50 | 52 | <div class="flex justify-between items-center"> |
51 | 53 | <div> |
52 | | - <h2 class="text-xl font-semibold text-gray-100">Connected Servers</h2> |
53 | | - <p class="text-sm text-gray-300 mt-1"> |
54 | | - Servers that have submitted times within the last 2 weeks |
| 54 | + <h2 class="text-base font-medium text-gray-100">Connected Servers</h2> |
| 55 | + <p class="text-sm text-gray-400"> |
| 56 | + |
55 | 57 | </p> |
56 | 58 | </div> |
57 | 59 | <div class="flex items-center space-x-3"> |
58 | 60 | <!-- API Key Creation Button --> |
59 | 61 | <button |
60 | 62 | v-if="canManageKeys" |
61 | 63 | @click="openCreateKeyModal" |
62 | | - class="px-4 py-2 text-sm font-medium text-gray-100 bg-green-700 rounded-md hover:bg-green-600 transition-colors cursor-pointer" |
| 64 | + class="px-4 py-2 text-sm text-gray-100 bg-green-700 rounded-md hover:bg-green-600 transition-colors cursor-pointer" |
63 | 65 | > |
64 | 66 | Create Server API Key |
65 | 67 | </button> |
66 | 68 | <button |
67 | 69 | @click="loadServers" |
68 | 70 | :disabled="loading" |
69 | | - class="px-4 py-2 text-sm font-medium text-gray-100 bg-main-600 rounded-md hover:bg-main-500 transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed" |
| 71 | + class="px-2.5 py-2 text-sm text-gray-100 bg-main-400 rounded-md hover:bg-main-300 transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed" |
70 | 72 | > |
71 | | - <div v-if="loading" class="flex items-center"> |
72 | | - <div class="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-100 mr-2"></div> |
73 | | - Loading... |
| 73 | + <div :class="loading ? 'animate-spin' : ''"> |
| 74 | + <svg |
| 75 | + xmlns="http://www.w3.org/2000/svg" |
| 76 | + width="20" |
| 77 | + height="20" |
| 78 | + viewBox="0 0 24 24" |
| 79 | + fill="none" |
| 80 | + stroke="currentColor" |
| 81 | + stroke-width="1.5" |
| 82 | + stroke-linecap="round" |
| 83 | + stroke-linejoin="round" |
| 84 | + class="text-gray-200" |
| 85 | + > |
| 86 | + <path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" /> |
| 87 | + <path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" /> |
| 88 | + </svg> |
74 | 89 | </div> |
75 | | - <span v-else>Refresh</span> |
76 | 90 | </button> |
77 | 91 | </div> |
78 | 92 | </div> |
79 | 93 | </div> |
80 | 94 |
|
81 | | - <div v-if="loading" class="p-8 text-center"> |
82 | | - <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-100 mx-auto"></div> |
| 95 | + <div v-if="loading" class="p-8 text-center flex flex-col items-center align-center"> |
| 96 | + <loadWheel></loadWheel> |
83 | 97 | <p class="mt-2 text-gray-300">Loading servers...</p> |
84 | 98 | </div> |
85 | 99 |
|
|
95 | 109 | <div |
96 | 110 | v-for="server in servers" |
97 | 111 | :key="server.server" |
98 | | - class="group p-6 hover:bg-main-600 transition-colors bg-main-700 odd:bg-main-800 relative" |
| 112 | + class="group p-6 py-4 hover:bg-main-500 transition-colors bg-main-600 odd:bg-main-700 relative" |
99 | 113 | > |
100 | 114 | <div class="flex items-center justify-between"> |
101 | 115 | <div class="flex items-center"> |
|
106 | 120 | ]" |
107 | 121 | ></div> |
108 | 122 | <div> |
109 | | - <h3 class="text-lg font-medium text-gray-100">{{ server.server }}</h3> |
| 123 | + <h3 class="text-base font-medium text-gray-100">{{ server.server }}</h3> |
110 | 124 | <div class="flex items-center flex-wrap gap-1 mt-1"> |
111 | 125 | <span |
112 | 126 | v-for="(ip, index) in server.ips" |
113 | 127 | :key="ip" |
114 | | - @click="copyToClipboard(ip)" |
115 | | - class="text-sm text-gray-100 monospace cursor-pointer hover:text-gray-200 hover:bg-main-600 px-1 py-0.5 rounded transition-colors" |
| 128 | + class="text-sm text-gray-200 monospace pr-1 py-0.5 " |
116 | 129 | :title="`Click to copy ${ip}`" |
117 | | - >{{ ip }}<span v-if="index < server.ips.length - 1" class="text-gray-400">,</span></span> |
118 | | - <span v-if="server.ips && server.ips.length > 0" class="text-xs text-gray-400 ml-2">(click to copy)</span> |
| 130 | + > |
| 131 | + <span @click="copyToClipboard(ip)" class="cursor-pointer hover:text-gray-200 hover:bg-main-200 transition-colors rounded p-0.5">{{ ip }}</span> |
| 132 | + <span v-if="index < server.ips.length - 1" class="text-gray-400 -ml-0.5">,</span> |
| 133 | + </span> |
119 | 134 | </div> |
120 | 135 | </div> |
121 | 136 | </div> |
|
125 | 140 | <button |
126 | 141 | v-if="canManageKeys" |
127 | 142 | @click="openEditKeyModal(server.server)" |
128 | | - class="opacity-0 group-hover:opacity-100 transition-opacity duration-200 px-3 py-1.5 text-xs font-medium text-gray-300 bg-main-800 hover:bg-main-700 border border-main-500 hover:border-main-400 rounded-md hover:text-gray-100 transition-colors cursor-pointer mr-3" |
| 143 | + class="opacity-0 group-hover:opacity-100 transition-opacity duration-200 px-3 py-1.5 text-xs font-medium text-gray-300 bg-main-800 hover:bg-main-700 border border-main-500 hover:border-main-400 rounded-md hover:text-gray-100 transition-colors cursor-pointer mr-3 min-w-24" |
129 | 144 | title="Edit API key" |
130 | 145 | > |
131 | 146 | <svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
138 | 153 | :class="[ |
139 | 154 | 'px-2 py-1 text-xs font-medium rounded-full', |
140 | 155 | server.active |
141 | | - ? 'bg-green-800 text-green-400' |
142 | | - : 'bg-red-800 text-red-400' |
| 156 | + ? 'bg-green-900 text-green-400' |
| 157 | + : 'bg-red-900 text-red-400' |
143 | 158 | ]" |
144 | 159 | > |
145 | 160 | {{ server.active ? 'Active' : 'Inactive' }} |
|
317 | 332 | <div v-else-if="loadingKeyInfo" class="mb-4"> |
318 | 333 | <label class="block text-sm font-medium text-gray-300 mb-2">Current API Key</label> |
319 | 334 | <div class="w-full px-3 py-2 bg-main-900 border border-main-600 rounded-md flex items-center"> |
320 | | - <div class="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-100 mr-2"></div> |
| 335 | + <loadWheel class="w-4 h-4 mr-2"></loadWheel> |
321 | 336 | <span class="text-sm text-gray-400">Loading key information...</span> |
322 | 337 | </div> |
323 | 338 | </div> |
@@ -428,6 +443,7 @@ import { KeyPermissions, addPermission } from '@/utils/permissions'; |
428 | 443 | import { canManageApiKeys } from '@/utils/userPermissions'; |
429 | 444 | import { useAuth } from '@/stores/auth'; |
430 | 445 | import Toast from '@/components/Toast.vue'; |
| 446 | +import loadWheel from '@/components/icons/loadWheel.vue'; |
431 | 447 |
|
432 | 448 | // Auth |
433 | 449 | const { user } = useAuth(); |
@@ -487,7 +503,7 @@ const loadServers = async () => { |
487 | 503 | try { |
488 | 504 | loading.value = true; |
489 | 505 | error.value = null; |
490 | | - servers.value = await OffstylesApi.getServers(); |
| 506 | + servers.value = (await OffstylesApi.getServers()).sort((a,b) => Number(b.active) - Number(a.active)); |
491 | 507 | } catch (err) { |
492 | 508 | error.value = err instanceof Error ? err.message : 'Failed to load servers'; |
493 | 509 | console.error('Failed to load servers:', err); |
|
0 commit comments