Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
10bc7f70
Unverified
Commit
10bc7f70
authored
Jan 15, 2026
by
Wesley Liddick
Committed by
GitHub
Jan 15, 2026
Browse files
Merge pull request #297 from LLLLLLiulei/feat/ip-management-enhancements
feat: add proxy geo location
parents
47eb3c88
aab44f9f
Changes
13
Show whitespace changes
Inline
Side-by-side
.gitignore
View file @
10bc7f70
...
@@ -83,6 +83,8 @@ temp/
...
@@ -83,6 +83,8 @@ temp/
*.log
*.log
*.bak
*.bak
.cache/
.cache/
.dev/
.serena/
# ===================
# ===================
# 构建产物
# 构建产物
...
...
backend/internal/handler/dto/mappers.go
View file @
10bc7f70
...
@@ -218,6 +218,11 @@ func ProxyWithAccountCountFromService(p *service.ProxyWithAccountCount) *ProxyWi
...
@@ -218,6 +218,11 @@ func ProxyWithAccountCountFromService(p *service.ProxyWithAccountCount) *ProxyWi
LatencyMs
:
p
.
LatencyMs
,
LatencyMs
:
p
.
LatencyMs
,
LatencyStatus
:
p
.
LatencyStatus
,
LatencyStatus
:
p
.
LatencyStatus
,
LatencyMessage
:
p
.
LatencyMessage
,
LatencyMessage
:
p
.
LatencyMessage
,
IPAddress
:
p
.
IPAddress
,
Country
:
p
.
Country
,
CountryCode
:
p
.
CountryCode
,
Region
:
p
.
Region
,
City
:
p
.
City
,
}
}
}
}
...
...
backend/internal/handler/dto/types.go
View file @
10bc7f70
...
@@ -134,6 +134,11 @@ type ProxyWithAccountCount struct {
...
@@ -134,6 +134,11 @@ type ProxyWithAccountCount struct {
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
LatencyStatus
string
`json:"latency_status,omitempty"`
LatencyStatus
string
`json:"latency_status,omitempty"`
LatencyMessage
string
`json:"latency_message,omitempty"`
LatencyMessage
string
`json:"latency_message,omitempty"`
IPAddress
string
`json:"ip_address,omitempty"`
Country
string
`json:"country,omitempty"`
CountryCode
string
`json:"country_code,omitempty"`
Region
string
`json:"region,omitempty"`
City
string
`json:"city,omitempty"`
}
}
type
ProxyAccountSummary
struct
{
type
ProxyAccountSummary
struct
{
...
...
backend/internal/repository/proxy_probe_service.go
View file @
10bc7f70
...
@@ -7,6 +7,7 @@ import (
...
@@ -7,6 +7,7 @@ import (
"io"
"io"
"log"
"log"
"net/http"
"net/http"
"strings"
"time"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/config"
...
@@ -35,7 +36,7 @@ func NewProxyExitInfoProber(cfg *config.Config) service.ProxyExitInfoProber {
...
@@ -35,7 +36,7 @@ func NewProxyExitInfoProber(cfg *config.Config) service.ProxyExitInfoProber {
}
}
const
(
const
(
defaultIPInfoURL
=
"http
s
://ip
info.io/json
"
defaultIPInfoURL
=
"http://ip
-api.com/json/?lang=zh-CN
"
defaultProxyProbeTimeout
=
30
*
time
.
Second
defaultProxyProbeTimeout
=
30
*
time
.
Second
)
)
...
@@ -78,10 +79,14 @@ func (s *proxyProbeService) ProbeProxy(ctx context.Context, proxyURL string) (*s
...
@@ -78,10 +79,14 @@ func (s *proxyProbeService) ProbeProxy(ctx context.Context, proxyURL string) (*s
}
}
var
ipInfo
struct
{
var
ipInfo
struct
{
IP
string
`json:"ip"`
Status
string
`json:"status"`
Message
string
`json:"message"`
Query
string
`json:"query"`
City
string
`json:"city"`
City
string
`json:"city"`
Region
string
`json:"region"`
Region
string
`json:"region"`
RegionName
string
`json:"regionName"`
Country
string
`json:"country"`
Country
string
`json:"country"`
CountryCode
string
`json:"countryCode"`
}
}
body
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
body
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
...
@@ -92,11 +97,22 @@ func (s *proxyProbeService) ProbeProxy(ctx context.Context, proxyURL string) (*s
...
@@ -92,11 +97,22 @@ func (s *proxyProbeService) ProbeProxy(ctx context.Context, proxyURL string) (*s
if
err
:=
json
.
Unmarshal
(
body
,
&
ipInfo
);
err
!=
nil
{
if
err
:=
json
.
Unmarshal
(
body
,
&
ipInfo
);
err
!=
nil
{
return
nil
,
latencyMs
,
fmt
.
Errorf
(
"failed to parse response: %w"
,
err
)
return
nil
,
latencyMs
,
fmt
.
Errorf
(
"failed to parse response: %w"
,
err
)
}
}
if
strings
.
ToLower
(
ipInfo
.
Status
)
!=
"success"
{
if
ipInfo
.
Message
==
""
{
ipInfo
.
Message
=
"ip-api request failed"
}
return
nil
,
latencyMs
,
fmt
.
Errorf
(
"ip-api request failed: %s"
,
ipInfo
.
Message
)
}
region
:=
ipInfo
.
RegionName
if
region
==
""
{
region
=
ipInfo
.
Region
}
return
&
service
.
ProxyExitInfo
{
return
&
service
.
ProxyExitInfo
{
IP
:
ipInfo
.
IP
,
IP
:
ipInfo
.
Query
,
City
:
ipInfo
.
City
,
City
:
ipInfo
.
City
,
Region
:
ipInfo
.
R
egion
,
Region
:
r
egion
,
Country
:
ipInfo
.
Country
,
Country
:
ipInfo
.
Country
,
CountryCode
:
ipInfo
.
CountryCode
,
},
latencyMs
,
nil
},
latencyMs
,
nil
}
}
backend/internal/repository/proxy_probe_service_test.go
View file @
10bc7f70
...
@@ -21,7 +21,7 @@ type ProxyProbeServiceSuite struct {
...
@@ -21,7 +21,7 @@ type ProxyProbeServiceSuite struct {
func
(
s
*
ProxyProbeServiceSuite
)
SetupTest
()
{
func
(
s
*
ProxyProbeServiceSuite
)
SetupTest
()
{
s
.
ctx
=
context
.
Background
()
s
.
ctx
=
context
.
Background
()
s
.
prober
=
&
proxyProbeService
{
s
.
prober
=
&
proxyProbeService
{
ipInfoURL
:
"http://ip
info
.test/json"
,
ipInfoURL
:
"http://ip
-api
.test/json
/?lang=zh-CN
"
,
allowPrivateHosts
:
true
,
allowPrivateHosts
:
true
,
}
}
}
}
...
@@ -54,7 +54,7 @@ func (s *ProxyProbeServiceSuite) TestProbeProxy_Success() {
...
@@ -54,7 +54,7 @@ func (s *ProxyProbeServiceSuite) TestProbeProxy_Success() {
s
.
setupProxyServer
(
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
s
.
setupProxyServer
(
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
seen
<-
r
.
RequestURI
seen
<-
r
.
RequestURI
w
.
Header
()
.
Set
(
"Content-Type"
,
"application/json"
)
w
.
Header
()
.
Set
(
"Content-Type"
,
"application/json"
)
_
,
_
=
io
.
WriteString
(
w
,
`{"
ip
":"1.2.3.4","city":"c","region":"r","country":"cc"}`
)
_
,
_
=
io
.
WriteString
(
w
,
`{"
status":"success","query
":"1.2.3.4","city":"c","region
Name
":"r","country":"cc"
,"countryCode":"CC"
}`
)
}))
}))
info
,
latencyMs
,
err
:=
s
.
prober
.
ProbeProxy
(
s
.
ctx
,
s
.
proxySrv
.
URL
)
info
,
latencyMs
,
err
:=
s
.
prober
.
ProbeProxy
(
s
.
ctx
,
s
.
proxySrv
.
URL
)
...
@@ -64,11 +64,12 @@ func (s *ProxyProbeServiceSuite) TestProbeProxy_Success() {
...
@@ -64,11 +64,12 @@ func (s *ProxyProbeServiceSuite) TestProbeProxy_Success() {
require
.
Equal
(
s
.
T
(),
"c"
,
info
.
City
)
require
.
Equal
(
s
.
T
(),
"c"
,
info
.
City
)
require
.
Equal
(
s
.
T
(),
"r"
,
info
.
Region
)
require
.
Equal
(
s
.
T
(),
"r"
,
info
.
Region
)
require
.
Equal
(
s
.
T
(),
"cc"
,
info
.
Country
)
require
.
Equal
(
s
.
T
(),
"cc"
,
info
.
Country
)
require
.
Equal
(
s
.
T
(),
"CC"
,
info
.
CountryCode
)
// Verify proxy received the request
// Verify proxy received the request
select
{
select
{
case
uri
:=
<-
seen
:
case
uri
:=
<-
seen
:
require
.
Contains
(
s
.
T
(),
uri
,
"ip
info
.test"
,
"expected request to go through proxy"
)
require
.
Contains
(
s
.
T
(),
uri
,
"ip
-api
.test"
,
"expected request to go through proxy"
)
default
:
default
:
require
.
Fail
(
s
.
T
(),
"expected proxy to receive request"
)
require
.
Fail
(
s
.
T
(),
"expected proxy to receive request"
)
}
}
...
...
backend/internal/service/admin_service.go
View file @
10bc7f70
...
@@ -243,14 +243,16 @@ type ProxyTestResult struct {
...
@@ -243,14 +243,16 @@ type ProxyTestResult struct {
City
string
`json:"city,omitempty"`
City
string
`json:"city,omitempty"`
Region
string
`json:"region,omitempty"`
Region
string
`json:"region,omitempty"`
Country
string
`json:"country,omitempty"`
Country
string
`json:"country,omitempty"`
CountryCode
string
`json:"country_code,omitempty"`
}
}
// ProxyExitInfo represents proxy exit information from ip
info.io
// ProxyExitInfo represents proxy exit information from ip
-api.com
type
ProxyExitInfo
struct
{
type
ProxyExitInfo
struct
{
IP
string
IP
string
City
string
City
string
Region
string
Region
string
Country
string
Country
string
CountryCode
string
}
}
// ProxyExitInfoProber tests proxy connectivity and retrieves exit information
// ProxyExitInfoProber tests proxy connectivity and retrieves exit information
...
@@ -1343,6 +1345,11 @@ func (s *adminServiceImpl) TestProxy(ctx context.Context, id int64) (*ProxyTestR
...
@@ -1343,6 +1345,11 @@ func (s *adminServiceImpl) TestProxy(ctx context.Context, id int64) (*ProxyTestR
Success
:
true
,
Success
:
true
,
LatencyMs
:
&
latency
,
LatencyMs
:
&
latency
,
Message
:
"Proxy is accessible"
,
Message
:
"Proxy is accessible"
,
IPAddress
:
exitInfo
.
IP
,
Country
:
exitInfo
.
Country
,
CountryCode
:
exitInfo
.
CountryCode
,
Region
:
exitInfo
.
Region
,
City
:
exitInfo
.
City
,
UpdatedAt
:
time
.
Now
(),
UpdatedAt
:
time
.
Now
(),
})
})
return
&
ProxyTestResult
{
return
&
ProxyTestResult
{
...
@@ -1353,6 +1360,7 @@ func (s *adminServiceImpl) TestProxy(ctx context.Context, id int64) (*ProxyTestR
...
@@ -1353,6 +1360,7 @@ func (s *adminServiceImpl) TestProxy(ctx context.Context, id int64) (*ProxyTestR
City
:
exitInfo
.
City
,
City
:
exitInfo
.
City
,
Region
:
exitInfo
.
Region
,
Region
:
exitInfo
.
Region
,
Country
:
exitInfo
.
Country
,
Country
:
exitInfo
.
Country
,
CountryCode
:
exitInfo
.
CountryCode
,
},
nil
},
nil
}
}
...
@@ -1360,7 +1368,7 @@ func (s *adminServiceImpl) probeProxyLatency(ctx context.Context, proxy *Proxy)
...
@@ -1360,7 +1368,7 @@ func (s *adminServiceImpl) probeProxyLatency(ctx context.Context, proxy *Proxy)
if
s
.
proxyProber
==
nil
||
proxy
==
nil
{
if
s
.
proxyProber
==
nil
||
proxy
==
nil
{
return
return
}
}
_
,
latencyMs
,
err
:=
s
.
proxyProber
.
ProbeProxy
(
ctx
,
proxy
.
URL
())
exitInfo
,
latencyMs
,
err
:=
s
.
proxyProber
.
ProbeProxy
(
ctx
,
proxy
.
URL
())
if
err
!=
nil
{
if
err
!=
nil
{
s
.
saveProxyLatency
(
ctx
,
proxy
.
ID
,
&
ProxyLatencyInfo
{
s
.
saveProxyLatency
(
ctx
,
proxy
.
ID
,
&
ProxyLatencyInfo
{
Success
:
false
,
Success
:
false
,
...
@@ -1375,6 +1383,11 @@ func (s *adminServiceImpl) probeProxyLatency(ctx context.Context, proxy *Proxy)
...
@@ -1375,6 +1383,11 @@ func (s *adminServiceImpl) probeProxyLatency(ctx context.Context, proxy *Proxy)
Success
:
true
,
Success
:
true
,
LatencyMs
:
&
latency
,
LatencyMs
:
&
latency
,
Message
:
"Proxy is accessible"
,
Message
:
"Proxy is accessible"
,
IPAddress
:
exitInfo
.
IP
,
Country
:
exitInfo
.
Country
,
CountryCode
:
exitInfo
.
CountryCode
,
Region
:
exitInfo
.
Region
,
City
:
exitInfo
.
City
,
UpdatedAt
:
time
.
Now
(),
UpdatedAt
:
time
.
Now
(),
})
})
}
}
...
@@ -1456,6 +1469,11 @@ func (s *adminServiceImpl) attachProxyLatency(ctx context.Context, proxies []Pro
...
@@ -1456,6 +1469,11 @@ func (s *adminServiceImpl) attachProxyLatency(ctx context.Context, proxies []Pro
proxies
[
i
]
.
LatencyStatus
=
"failed"
proxies
[
i
]
.
LatencyStatus
=
"failed"
}
}
proxies
[
i
]
.
LatencyMessage
=
info
.
Message
proxies
[
i
]
.
LatencyMessage
=
info
.
Message
proxies
[
i
]
.
IPAddress
=
info
.
IPAddress
proxies
[
i
]
.
Country
=
info
.
Country
proxies
[
i
]
.
CountryCode
=
info
.
CountryCode
proxies
[
i
]
.
Region
=
info
.
Region
proxies
[
i
]
.
City
=
info
.
City
}
}
}
}
...
...
backend/internal/service/proxy.go
View file @
10bc7f70
...
@@ -35,6 +35,11 @@ type ProxyWithAccountCount struct {
...
@@ -35,6 +35,11 @@ type ProxyWithAccountCount struct {
LatencyMs
*
int64
LatencyMs
*
int64
LatencyStatus
string
LatencyStatus
string
LatencyMessage
string
LatencyMessage
string
IPAddress
string
Country
string
CountryCode
string
Region
string
City
string
}
}
type
ProxyAccountSummary
struct
{
type
ProxyAccountSummary
struct
{
...
...
backend/internal/service/proxy_latency_cache.go
View file @
10bc7f70
...
@@ -9,6 +9,11 @@ type ProxyLatencyInfo struct {
...
@@ -9,6 +9,11 @@ type ProxyLatencyInfo struct {
Success
bool
`json:"success"`
Success
bool
`json:"success"`
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
Message
string
`json:"message,omitempty"`
Message
string
`json:"message,omitempty"`
IPAddress
string
`json:"ip_address,omitempty"`
Country
string
`json:"country,omitempty"`
CountryCode
string
`json:"country_code,omitempty"`
Region
string
`json:"region,omitempty"`
City
string
`json:"city,omitempty"`
UpdatedAt
time
.
Time
`json:"updated_at"`
UpdatedAt
time
.
Time
`json:"updated_at"`
}
}
...
...
frontend/src/api/admin/proxies.ts
View file @
10bc7f70
...
@@ -126,6 +126,7 @@ export async function testProxy(id: number): Promise<{
...
@@ -126,6 +126,7 @@ export async function testProxy(id: number): Promise<{
city
?:
string
city
?:
string
region
?:
string
region
?:
string
country
?:
string
country
?:
string
country_code
?:
string
}
>
{
}
>
{
const
{
data
}
=
await
apiClient
.
post
<
{
const
{
data
}
=
await
apiClient
.
post
<
{
success
:
boolean
success
:
boolean
...
@@ -135,6 +136,7 @@ export async function testProxy(id: number): Promise<{
...
@@ -135,6 +136,7 @@ export async function testProxy(id: number): Promise<{
city
?:
string
city
?:
string
region
?:
string
region
?:
string
country
?:
string
country
?:
string
country_code
?:
string
}
>
(
`/admin/proxies/
${
id
}
/test`
)
}
>
(
`/admin/proxies/
${
id
}
/test`
)
return
data
return
data
}
}
...
...
frontend/src/i18n/locales/en.ts
View file @
10bc7f70
...
@@ -1634,6 +1634,7 @@ export default {
...
@@ -1634,6 +1634,7 @@ export default {
name
:
'
Name
'
,
name
:
'
Name
'
,
protocol
:
'
Protocol
'
,
protocol
:
'
Protocol
'
,
address
:
'
Address
'
,
address
:
'
Address
'
,
location
:
'
Location
'
,
status
:
'
Status
'
,
status
:
'
Status
'
,
accounts
:
'
Accounts
'
,
accounts
:
'
Accounts
'
,
latency
:
'
Latency
'
,
latency
:
'
Latency
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
10bc7f70
...
@@ -1719,6 +1719,7 @@ export default {
...
@@ -1719,6 +1719,7 @@ export default {
name
:
'
名称
'
,
name
:
'
名称
'
,
protocol
:
'
协议
'
,
protocol
:
'
协议
'
,
address
:
'
地址
'
,
address
:
'
地址
'
,
location
:
'
地理位置
'
,
status
:
'
状态
'
,
status
:
'
状态
'
,
accounts
:
'
账号数
'
,
accounts
:
'
账号数
'
,
latency
:
'
延迟
'
,
latency
:
'
延迟
'
,
...
...
frontend/src/types/index.ts
View file @
10bc7f70
...
@@ -367,6 +367,11 @@ export interface Proxy {
...
@@ -367,6 +367,11 @@ export interface Proxy {
latency_ms
?:
number
latency_ms
?:
number
latency_status
?:
'
success
'
|
'
failed
'
latency_status
?:
'
success
'
|
'
failed
'
latency_message
?:
string
latency_message
?:
string
ip_address
?:
string
country
?:
string
country_code
?:
string
region
?:
string
city
?:
string
created_at
:
string
created_at
:
string
updated_at
:
string
updated_at
:
string
}
}
...
...
frontend/src/views/admin/ProxiesView.vue
View file @
10bc7f70
...
@@ -117,6 +117,21 @@
...
@@ -117,6 +117,21 @@
<code
class=
"code text-xs"
>
{{
row
.
host
}}
:
{{
row
.
port
}}
</code>
<code
class=
"code text-xs"
>
{{
row
.
host
}}
:
{{
row
.
port
}}
</code>
</
template
>
</
template
>
<
template
#cell-location=
"{ row }"
>
<div
class=
"flex items-center gap-2"
>
<img
v-if=
"row.country_code"
:src=
"flagUrl(row.country_code)"
:alt=
"row.country || row.country_code"
class=
"h-4 w-6 rounded-sm"
/>
<span
v-if=
"formatLocation(row)"
class=
"text-sm text-gray-700 dark:text-gray-200"
>
{{
formatLocation
(
row
)
}}
</span>
<span
v-else
class=
"text-sm text-gray-400"
>
-
</span>
</div>
</
template
>
<
template
#cell-account_count=
"{ row, value }"
>
<
template
#cell-account_count=
"{ row, value }"
>
<button
<button
v-if=
"(value || 0) > 0"
v-if=
"(value || 0) > 0"
...
@@ -665,6 +680,7 @@ const columns = computed<Column[]>(() => [
...
@@ -665,6 +680,7 @@ const columns = computed<Column[]>(() => [
{
key
:
'
name
'
,
label
:
t
(
'
admin.proxies.columns.name
'
),
sortable
:
true
}
,
{
key
:
'
name
'
,
label
:
t
(
'
admin.proxies.columns.name
'
),
sortable
:
true
}
,
{
key
:
'
protocol
'
,
label
:
t
(
'
admin.proxies.columns.protocol
'
),
sortable
:
true
}
,
{
key
:
'
protocol
'
,
label
:
t
(
'
admin.proxies.columns.protocol
'
),
sortable
:
true
}
,
{
key
:
'
address
'
,
label
:
t
(
'
admin.proxies.columns.address
'
),
sortable
:
false
}
,
{
key
:
'
address
'
,
label
:
t
(
'
admin.proxies.columns.address
'
),
sortable
:
false
}
,
{
key
:
'
location
'
,
label
:
t
(
'
admin.proxies.columns.location
'
),
sortable
:
false
}
,
{
key
:
'
account_count
'
,
label
:
t
(
'
admin.proxies.columns.accounts
'
),
sortable
:
true
}
,
{
key
:
'
account_count
'
,
label
:
t
(
'
admin.proxies.columns.accounts
'
),
sortable
:
true
}
,
{
key
:
'
latency
'
,
label
:
t
(
'
admin.proxies.columns.latency
'
),
sortable
:
false
}
,
{
key
:
'
latency
'
,
label
:
t
(
'
admin.proxies.columns.latency
'
),
sortable
:
false
}
,
{
key
:
'
status
'
,
label
:
t
(
'
admin.proxies.columns.status
'
),
sortable
:
true
}
,
{
key
:
'
status
'
,
label
:
t
(
'
admin.proxies.columns.status
'
),
sortable
:
true
}
,
...
@@ -1058,20 +1074,47 @@ const handleUpdateProxy = async () => {
...
@@ -1058,20 +1074,47 @@ const handleUpdateProxy = async () => {
const
applyLatencyResult
=
(
const
applyLatencyResult
=
(
proxyId
:
number
,
proxyId
:
number
,
result
:
{
success
:
boolean
;
latency_ms
?:
number
;
message
?:
string
}
result
:
{
success
:
boolean
latency_ms
?:
number
message
?:
string
ip_address
?:
string
country
?:
string
country_code
?:
string
region
?:
string
city
?:
string
}
)
=>
{
)
=>
{
const
target
=
proxies
.
value
.
find
((
proxy
)
=>
proxy
.
id
===
proxyId
)
const
target
=
proxies
.
value
.
find
((
proxy
)
=>
proxy
.
id
===
proxyId
)
if
(
!
target
)
return
if
(
!
target
)
return
if
(
result
.
success
)
{
if
(
result
.
success
)
{
target
.
latency_status
=
'
success
'
target
.
latency_status
=
'
success
'
target
.
latency_ms
=
result
.
latency_ms
target
.
latency_ms
=
result
.
latency_ms
target
.
ip_address
=
result
.
ip_address
target
.
country
=
result
.
country
target
.
country_code
=
result
.
country_code
target
.
region
=
result
.
region
target
.
city
=
result
.
city
}
else
{
}
else
{
target
.
latency_status
=
'
failed
'
target
.
latency_status
=
'
failed
'
target
.
latency_ms
=
undefined
target
.
latency_ms
=
undefined
target
.
ip_address
=
undefined
target
.
country
=
undefined
target
.
country_code
=
undefined
target
.
region
=
undefined
target
.
city
=
undefined
}
}
target
.
latency_message
=
result
.
message
target
.
latency_message
=
result
.
message
}
}
const
formatLocation
=
(
proxy
:
Proxy
)
=>
{
const
parts
=
[
proxy
.
country
,
proxy
.
city
].
filter
(
Boolean
)
as
string
[]
return
parts
.
join
(
'
·
'
)
}
const
flagUrl
=
(
code
:
string
)
=>
`https://unpkg.com/flag-icons/flags/4x3/${code.toLowerCase()
}
.svg`
const
startTestingProxy
=
(
proxyId
:
number
)
=>
{
const
startTestingProxy
=
(
proxyId
:
number
)
=>
{
testingProxyIds
.
value
=
new
Set
([...
testingProxyIds
.
value
,
proxyId
])
testingProxyIds
.
value
=
new
Set
([...
testingProxyIds
.
value
,
proxyId
])
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment