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:
MHSanaei
2026-05-11 10:06:01 +02:00
parent 3e8a0eb93e
commit 5f3e9ed0ea
2 changed files with 38 additions and 5 deletions

View File

@@ -25,6 +25,9 @@ func (s *NordService) GetCountries() (string, error) {
return "", err
}
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))
if err != nil {
return "", err
@@ -45,6 +48,9 @@ func (s *NordService) GetServers(countryId string) (string, error) {
return "", err
}
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))
if err != nil {
return "", err
@@ -97,8 +103,7 @@ func (s *NordService) GetCredentials(token string) (string, error) {
}
req.SetBasicAuth("token", token)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
resp, err := nordHTTPClient.Do(req)
if err != nil {
return "", err
}