mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-13 09:36:05 +00:00
feat(xray/nord): searchable server list + colored load tag, surface API errors
Frontend (NordModal.vue):
- Server selector gets show-search with the option label set to
`${cityName} ${name} ${hostname}` so admins can find a specific
server inside a 100+ entry country list by typing.
- Each option renders the load as a colored a-tag (green <30%,
orange 30-70%, red >70%) instead of plain text — quicker visual
scan when sorting through servers in the dropdown.
Backend (nord.go):
- GetCountries / GetServers now check resp.StatusCode and return
"NordVPN API error: <status>" on non-200, matching the pattern
GetCredentials already used. Previously a 4xx/5xx body was
returned as a "success" string and the frontend silently failed
to parse it, surfacing only as an empty "No servers found".
- GetCredentials drops its own ad-hoc 10s http.Client and reuses
the shared nordHTTPClient (15s) — one client, one timeout.
This commit is contained in:
parent
3e8a0eb93e
commit
5f3e9ed0ea
2 changed files with 38 additions and 5 deletions
|
|
@ -222,6 +222,12 @@ function resetOutbound() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() { emit('update:open', false); }
|
function close() { emit('update:open', false); }
|
||||||
|
|
||||||
|
function loadColor(load) {
|
||||||
|
if (load < 30) return 'green';
|
||||||
|
if (load < 70) return 'orange';
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -299,9 +305,13 @@ function close() { emit('update:open', false); }
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item v-if="filteredServers.length > 0" label="Server">
|
<a-form-item v-if="filteredServers.length > 0" label="Server">
|
||||||
<a-select v-model:value="serverId">
|
<a-select v-model:value="serverId" show-search option-filter-prop="label">
|
||||||
<a-select-option v-for="s in filteredServers" :key="s.id" :value="s.id">
|
<a-select-option v-for="s in filteredServers" :key="s.id" :value="s.id"
|
||||||
{{ s.cityName }} - {{ s.name }} (load: {{ s.load }}%)
|
:label="`${s.cityName} ${s.name} ${s.hostname}`">
|
||||||
|
<span class="server-row">
|
||||||
|
<span class="server-name">{{ s.cityName }} - {{ s.name }}</span>
|
||||||
|
<a-tag :color="loadColor(s.load)" class="server-load-tag">{{ s.load }}%</a-tag>
|
||||||
|
</span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
@ -376,4 +386,22 @@ function close() { emit('update:open', false); }
|
||||||
.ml-8 {
|
.ml-8 {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-row {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-load-tag {
|
||||||
|
margin-right: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ func (s *NordService) GetCountries() (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
|
||||||
|
}
|
||||||
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -45,6 +48,9 @@ func (s *NordService) GetServers(countryId string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", common.NewErrorf("NordVPN API error: %s", resp.Status)
|
||||||
|
}
|
||||||
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -97,8 +103,7 @@ func (s *NordService) GetCredentials(token string) (string, error) {
|
||||||
}
|
}
|
||||||
req.SetBasicAuth("token", token)
|
req.SetBasicAuth("token", token)
|
||||||
|
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
resp, err := nordHTTPClient.Do(req)
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue