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
3613695f
Unverified
Commit
3613695f
authored
Mar 01, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 01, 2026
Browse files
Merge pull request #697 from DaydreamCoding/feat/proxy-password-visibility
feat(admin): 代理密码可见性 + 复制代理 URL 功能
parents
dd8df483
8fb7d476
Changes
6
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/proxy_handler.go
View file @
3613695f
...
@@ -64,9 +64,9 @@ func (h *ProxyHandler) List(c *gin.Context) {
...
@@ -64,9 +64,9 @@ func (h *ProxyHandler) List(c *gin.Context) {
return
return
}
}
out
:=
make
([]
dto
.
ProxyWithAccountCount
,
0
,
len
(
proxies
))
out
:=
make
([]
dto
.
Admin
ProxyWithAccountCount
,
0
,
len
(
proxies
))
for
i
:=
range
proxies
{
for
i
:=
range
proxies
{
out
=
append
(
out
,
*
dto
.
ProxyWithAccountCountFromService
(
&
proxies
[
i
]))
out
=
append
(
out
,
*
dto
.
ProxyWithAccountCountFromService
Admin
(
&
proxies
[
i
]))
}
}
response
.
Paginated
(
c
,
out
,
total
,
page
,
pageSize
)
response
.
Paginated
(
c
,
out
,
total
,
page
,
pageSize
)
}
}
...
@@ -83,9 +83,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) {
...
@@ -83,9 +83,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) {
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
}
}
out
:=
make
([]
dto
.
ProxyWithAccountCount
,
0
,
len
(
proxies
))
out
:=
make
([]
dto
.
Admin
ProxyWithAccountCount
,
0
,
len
(
proxies
))
for
i
:=
range
proxies
{
for
i
:=
range
proxies
{
out
=
append
(
out
,
*
dto
.
ProxyWithAccountCountFromService
(
&
proxies
[
i
]))
out
=
append
(
out
,
*
dto
.
ProxyWithAccountCountFromService
Admin
(
&
proxies
[
i
]))
}
}
response
.
Success
(
c
,
out
)
response
.
Success
(
c
,
out
)
return
return
...
@@ -97,9 +97,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) {
...
@@ -97,9 +97,9 @@ func (h *ProxyHandler) GetAll(c *gin.Context) {
return
return
}
}
out
:=
make
([]
dto
.
Proxy
,
0
,
len
(
proxies
))
out
:=
make
([]
dto
.
Admin
Proxy
,
0
,
len
(
proxies
))
for
i
:=
range
proxies
{
for
i
:=
range
proxies
{
out
=
append
(
out
,
*
dto
.
ProxyFromService
(
&
proxies
[
i
]))
out
=
append
(
out
,
*
dto
.
ProxyFromService
Admin
(
&
proxies
[
i
]))
}
}
response
.
Success
(
c
,
out
)
response
.
Success
(
c
,
out
)
}
}
...
@@ -119,7 +119,7 @@ func (h *ProxyHandler) GetByID(c *gin.Context) {
...
@@ -119,7 +119,7 @@ func (h *ProxyHandler) GetByID(c *gin.Context) {
return
return
}
}
response
.
Success
(
c
,
dto
.
ProxyFromService
(
proxy
))
response
.
Success
(
c
,
dto
.
ProxyFromService
Admin
(
proxy
))
}
}
// Create handles creating a new proxy
// Create handles creating a new proxy
...
@@ -143,7 +143,7 @@ func (h *ProxyHandler) Create(c *gin.Context) {
...
@@ -143,7 +143,7 @@ func (h *ProxyHandler) Create(c *gin.Context) {
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
return
dto
.
ProxyFromService
(
proxy
),
nil
return
dto
.
ProxyFromService
Admin
(
proxy
),
nil
})
})
}
}
...
@@ -176,7 +176,7 @@ func (h *ProxyHandler) Update(c *gin.Context) {
...
@@ -176,7 +176,7 @@ func (h *ProxyHandler) Update(c *gin.Context) {
return
return
}
}
response
.
Success
(
c
,
dto
.
ProxyFromService
(
proxy
))
response
.
Success
(
c
,
dto
.
ProxyFromService
Admin
(
proxy
))
}
}
// Delete handles deleting a proxy
// Delete handles deleting a proxy
...
...
backend/internal/handler/dto/mappers.go
View file @
3613695f
...
@@ -293,7 +293,6 @@ func ProxyFromService(p *service.Proxy) *Proxy {
...
@@ -293,7 +293,6 @@ func ProxyFromService(p *service.Proxy) *Proxy {
Host
:
p
.
Host
,
Host
:
p
.
Host
,
Port
:
p
.
Port
,
Port
:
p
.
Port
,
Username
:
p
.
Username
,
Username
:
p
.
Username
,
Password
:
p
.
Password
,
Status
:
p
.
Status
,
Status
:
p
.
Status
,
CreatedAt
:
p
.
CreatedAt
,
CreatedAt
:
p
.
CreatedAt
,
UpdatedAt
:
p
.
UpdatedAt
,
UpdatedAt
:
p
.
UpdatedAt
,
...
@@ -323,6 +322,51 @@ func ProxyWithAccountCountFromService(p *service.ProxyWithAccountCount) *ProxyWi
...
@@ -323,6 +322,51 @@ func ProxyWithAccountCountFromService(p *service.ProxyWithAccountCount) *ProxyWi
}
}
}
}
// ProxyFromServiceAdmin converts a service Proxy to AdminProxy DTO for admin users.
// It includes the password field - user-facing endpoints must not use this.
func
ProxyFromServiceAdmin
(
p
*
service
.
Proxy
)
*
AdminProxy
{
if
p
==
nil
{
return
nil
}
base
:=
ProxyFromService
(
p
)
if
base
==
nil
{
return
nil
}
return
&
AdminProxy
{
Proxy
:
*
base
,
Password
:
p
.
Password
,
}
}
// ProxyWithAccountCountFromServiceAdmin converts a service ProxyWithAccountCount to AdminProxyWithAccountCount DTO.
// It includes the password field - user-facing endpoints must not use this.
func
ProxyWithAccountCountFromServiceAdmin
(
p
*
service
.
ProxyWithAccountCount
)
*
AdminProxyWithAccountCount
{
if
p
==
nil
{
return
nil
}
admin
:=
ProxyFromServiceAdmin
(
&
p
.
Proxy
)
if
admin
==
nil
{
return
nil
}
return
&
AdminProxyWithAccountCount
{
AdminProxy
:
*
admin
,
AccountCount
:
p
.
AccountCount
,
LatencyMs
:
p
.
LatencyMs
,
LatencyStatus
:
p
.
LatencyStatus
,
LatencyMessage
:
p
.
LatencyMessage
,
IPAddress
:
p
.
IPAddress
,
Country
:
p
.
Country
,
CountryCode
:
p
.
CountryCode
,
Region
:
p
.
Region
,
City
:
p
.
City
,
QualityStatus
:
p
.
QualityStatus
,
QualityScore
:
p
.
QualityScore
,
QualityGrade
:
p
.
QualityGrade
,
QualitySummary
:
p
.
QualitySummary
,
QualityChecked
:
p
.
QualityChecked
,
}
}
func
ProxyAccountSummaryFromService
(
a
*
service
.
ProxyAccountSummary
)
*
ProxyAccountSummary
{
func
ProxyAccountSummaryFromService
(
a
*
service
.
ProxyAccountSummary
)
*
ProxyAccountSummary
{
if
a
==
nil
{
if
a
==
nil
{
return
nil
return
nil
...
...
backend/internal/handler/dto/types.go
View file @
3613695f
...
@@ -221,6 +221,32 @@ type ProxyWithAccountCount struct {
...
@@ -221,6 +221,32 @@ type ProxyWithAccountCount struct {
QualityChecked
*
int64
`json:"quality_checked,omitempty"`
QualityChecked
*
int64
`json:"quality_checked,omitempty"`
}
}
// AdminProxy 是管理员接口使用的 proxy DTO(包含密码等敏感字段)。
// 注意:普通接口不得使用此 DTO。
type
AdminProxy
struct
{
Proxy
Password
string
`json:"password,omitempty"`
}
// AdminProxyWithAccountCount 是管理员接口使用的带账号统计的 proxy DTO。
type
AdminProxyWithAccountCount
struct
{
AdminProxy
AccountCount
int64
`json:"account_count"`
LatencyMs
*
int64
`json:"latency_ms,omitempty"`
LatencyStatus
string
`json:"latency_status,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"`
QualityStatus
string
`json:"quality_status,omitempty"`
QualityScore
*
int
`json:"quality_score,omitempty"`
QualityGrade
string
`json:"quality_grade,omitempty"`
QualitySummary
string
`json:"quality_summary,omitempty"`
QualityChecked
*
int64
`json:"quality_checked,omitempty"`
}
type
ProxyAccountSummary
struct
{
type
ProxyAccountSummary
struct
{
ID
int64
`json:"id"`
ID
int64
`json:"id"`
Name
string
`json:"name"`
Name
string
`json:"name"`
...
...
frontend/src/i18n/locales/en.ts
View file @
3613695f
...
@@ -2345,6 +2345,8 @@ export default {
...
@@ -2345,6 +2345,8 @@ export default {
dataExportConfirm
:
'
Confirm Export
'
,
dataExportConfirm
:
'
Confirm Export
'
,
dataExported
:
'
Data exported successfully
'
,
dataExported
:
'
Data exported successfully
'
,
dataExportFailed
:
'
Failed to export data
'
,
dataExportFailed
:
'
Failed to export data
'
,
copyProxyUrl
:
'
Copy Proxy URL
'
,
urlCopied
:
'
Proxy URL copied
'
,
searchProxies
:
'
Search proxies...
'
,
searchProxies
:
'
Search proxies...
'
,
allProtocols
:
'
All Protocols
'
,
allProtocols
:
'
All Protocols
'
,
allStatus
:
'
All Status
'
,
allStatus
:
'
All Status
'
,
...
@@ -2358,6 +2360,7 @@ export default {
...
@@ -2358,6 +2360,7 @@ export default {
name
:
'
Name
'
,
name
:
'
Name
'
,
protocol
:
'
Protocol
'
,
protocol
:
'
Protocol
'
,
address
:
'
Address
'
,
address
:
'
Address
'
,
auth
:
'
Auth
'
,
location
:
'
Location
'
,
location
:
'
Location
'
,
status
:
'
Status
'
,
status
:
'
Status
'
,
accounts
:
'
Accounts
'
,
accounts
:
'
Accounts
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
3613695f
...
@@ -2459,6 +2459,7 @@ export default {
...
@@ -2459,6 +2459,7 @@ export default {
name
:
'
名称
'
,
name
:
'
名称
'
,
protocol
:
'
协议
'
,
protocol
:
'
协议
'
,
address
:
'
地址
'
,
address
:
'
地址
'
,
auth
:
'
认证
'
,
location
:
'
地理位置
'
,
location
:
'
地理位置
'
,
status
:
'
状态
'
,
status
:
'
状态
'
,
accounts
:
'
账号数
'
,
accounts
:
'
账号数
'
,
...
@@ -2486,6 +2487,8 @@ export default {
...
@@ -2486,6 +2487,8 @@ export default {
allStatuses
:
'
全部状态
'
allStatuses
:
'
全部状态
'
},
},
// Additional keys used in ProxiesView
// Additional keys used in ProxiesView
copyProxyUrl
:
'
复制代理 URL
'
,
urlCopied
:
'
代理 URL 已复制
'
,
allProtocols
:
'
全部协议
'
,
allProtocols
:
'
全部协议
'
,
allStatus
:
'
全部状态
'
,
allStatus
:
'
全部状态
'
,
searchProxies
:
'
搜索代理...
'
,
searchProxies
:
'
搜索代理...
'
,
...
...
frontend/src/views/admin/ProxiesView.vue
View file @
3613695f
...
@@ -124,7 +124,54 @@
...
@@ -124,7 +124,54 @@
</
template
>
</
template
>
<
template
#cell-address=
"{ row }"
>
<
template
#cell-address=
"{ row }"
>
<code
class=
"code text-xs"
>
{{
row
.
host
}}
:
{{
row
.
port
}}
</code>
<div
class=
"flex items-center gap-1.5"
>
<code
class=
"code text-xs"
>
{{
row
.
host
}}
:
{{
row
.
port
}}
</code>
<div
class=
"relative"
>
<button
type=
"button"
class=
"rounded p-0.5 text-gray-400 hover:text-primary-600 dark:hover:text-primary-400"
:title=
"t('admin.proxies.copyProxyUrl')"
@
click.stop=
"copyProxyUrl(row)"
@
contextmenu.prevent=
"toggleCopyMenu(row.id)"
>
<Icon
name=
"copy"
size=
"sm"
/>
</button>
<!-- 右键展开格式选择菜单 -->
<div
v-if=
"copyMenuProxyId === row.id"
class=
"absolute left-0 top-full z-50 mt-1 w-auto min-w-[180px] rounded-lg border border-gray-200 bg-white py-1 shadow-lg dark:border-dark-500 dark:bg-dark-700"
>
<button
v-for=
"fmt in getCopyFormats(row)"
:key=
"fmt.label"
class=
"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs hover:bg-gray-100 dark:hover:bg-dark-600"
@
click.stop=
"copyFormat(fmt.value)"
>
<span
class=
"truncate font-mono text-gray-600 dark:text-gray-300"
>
{{
fmt
.
label
}}
</span>
</button>
</div>
</div>
</div>
</
template
>
<
template
#cell-auth=
"{ row }"
>
<div
v-if=
"row.username || row.password"
class=
"flex items-center gap-1.5"
>
<div
class=
"flex flex-col text-xs"
>
<span
v-if=
"row.username"
class=
"text-gray-700 dark:text-gray-200"
>
{{
row
.
username
}}
</span>
<span
v-if=
"row.password"
class=
"font-mono text-gray-500 dark:text-gray-400"
>
{{
visiblePasswordIds
.
has
(
row
.
id
)
?
row
.
password
:
'
••••••
'
}}
</span>
</div>
<button
v-if=
"row.password"
type=
"button"
class=
"ml-1 rounded p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
@
click.stop=
"visiblePasswordIds.has(row.id) ? visiblePasswordIds.delete(row.id) : visiblePasswordIds.add(row.id)"
>
<Icon
:name=
"visiblePasswordIds.has(row.id) ? 'eyeOff' : 'eye'"
size=
"sm"
/>
</button>
</div>
<span
v-else
class=
"text-sm text-gray-400"
>
-
</span>
</
template
>
</
template
>
<
template
#cell-location=
"{ row }"
>
<
template
#cell-location=
"{ row }"
>
...
@@ -397,12 +444,21 @@
...
@@ -397,12 +444,21 @@
<
/div
>
<
/div
>
<
div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.proxies.password
'
)
}}
<
/label
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.proxies.password
'
)
}}
<
/label
>
<
input
<
div
class
=
"
relative
"
>
v
-
model
=
"
createForm.password
"
<
input
type
=
"
password
"
v
-
model
=
"
createForm.password
"
class
=
"
input
"
:
type
=
"
createPasswordVisible ? 'text' : 'password'
"
:
placeholder
=
"
t('admin.proxies.optionalAuth')
"
class
=
"
input pr-10
"
/>
:
placeholder
=
"
t('admin.proxies.optionalAuth')
"
/>
<
button
type
=
"
button
"
class
=
"
absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300
"
@
click
=
"
createPasswordVisible = !createPasswordVisible
"
>
<
Icon
:
name
=
"
createPasswordVisible ? 'eyeOff' : 'eye'
"
size
=
"
md
"
/>
<
/button
>
<
/div
>
<
/div
>
<
/div
>
<
/form
>
<
/form
>
...
@@ -581,12 +637,22 @@
...
@@ -581,12 +637,22 @@
<
/div
>
<
/div
>
<
div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.proxies.password
'
)
}}
<
/label
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.proxies.password
'
)
}}
<
/label
>
<
input
<
div
class
=
"
relative
"
>
v
-
model
=
"
editForm.password
"
<
input
type
=
"
password
"
v
-
model
=
"
editForm.password
"
:
placeholder
=
"
t('admin.proxies.leaveEmptyToKeep')
"
:
type
=
"
editPasswordVisible ? 'text' : 'password'
"
class
=
"
input
"
:
placeholder
=
"
t('admin.proxies.leaveEmptyToKeep')
"
/>
class
=
"
input pr-10
"
@
input
=
"
editPasswordDirty = true
"
/>
<
button
type
=
"
button
"
class
=
"
absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300
"
@
click
=
"
editPasswordVisible = !editPasswordVisible
"
>
<
Icon
:
name
=
"
editPasswordVisible ? 'eyeOff' : 'eye'
"
size
=
"
md
"
/>
<
/button
>
<
/div
>
<
/div
>
<
/div
>
<
div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.proxies.status
'
)
}}
<
/label
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.proxies.status
'
)
}}
<
/label
>
...
@@ -813,15 +879,18 @@ import ImportDataModal from '@/components/admin/proxy/ImportDataModal.vue'
...
@@ -813,15 +879,18 @@ import ImportDataModal from '@/components/admin/proxy/ImportDataModal.vue'
import
Select
from
'
@/components/common/Select.vue
'
import
Select
from
'
@/components/common/Select.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
PlatformTypeBadge
from
'
@/components/common/PlatformTypeBadge.vue
'
import
PlatformTypeBadge
from
'
@/components/common/PlatformTypeBadge.vue
'
import
{
useClipboard
}
from
'
@/composables/useClipboard
'
const
{
t
}
=
useI18n
()
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
const
appStore
=
useAppStore
()
const
{
copyToClipboard
}
=
useClipboard
()
const
columns
=
computed
<
Column
[]
>
(()
=>
[
const
columns
=
computed
<
Column
[]
>
(()
=>
[
{
key
:
'
select
'
,
label
:
''
,
sortable
:
false
}
,
{
key
:
'
select
'
,
label
:
''
,
sortable
:
false
}
,
{
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
:
'
auth
'
,
label
:
t
(
'
admin.proxies.columns.auth
'
),
sortable
:
false
}
,
{
key
:
'
location
'
,
label
:
t
(
'
admin.proxies.columns.location
'
),
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
}
,
...
@@ -858,6 +927,8 @@ const editStatusOptions = computed(() => [
...
@@ -858,6 +927,8 @@ const editStatusOptions = computed(() => [
])
])
const
proxies
=
ref
<
Proxy
[]
>
([])
const
proxies
=
ref
<
Proxy
[]
>
([])
const
visiblePasswordIds
=
reactive
(
new
Set
<
number
>
())
const
copyMenuProxyId
=
ref
<
number
|
null
>
(
null
)
const
loading
=
ref
(
false
)
const
loading
=
ref
(
false
)
const
searchQuery
=
ref
(
''
)
const
searchQuery
=
ref
(
''
)
const
filters
=
reactive
({
const
filters
=
reactive
({
...
@@ -872,7 +943,10 @@ const pagination = reactive({
...
@@ -872,7 +943,10 @@ const pagination = reactive({
}
)
}
)
const
showCreateModal
=
ref
(
false
)
const
showCreateModal
=
ref
(
false
)
const
createPasswordVisible
=
ref
(
false
)
const
showEditModal
=
ref
(
false
)
const
showEditModal
=
ref
(
false
)
const
editPasswordVisible
=
ref
(
false
)
const
editPasswordDirty
=
ref
(
false
)
const
showImportData
=
ref
(
false
)
const
showImportData
=
ref
(
false
)
const
showDeleteDialog
=
ref
(
false
)
const
showDeleteDialog
=
ref
(
false
)
const
showBatchDeleteDialog
=
ref
(
false
)
const
showBatchDeleteDialog
=
ref
(
false
)
...
@@ -1030,6 +1104,7 @@ const closeCreateModal = () => {
...
@@ -1030,6 +1104,7 @@ const closeCreateModal = () => {
createForm
.
port
=
8080
createForm
.
port
=
8080
createForm
.
username
=
''
createForm
.
username
=
''
createForm
.
password
=
''
createForm
.
password
=
''
createPasswordVisible
.
value
=
false
batchInput
.
value
=
''
batchInput
.
value
=
''
batchParseResult
.
total
=
0
batchParseResult
.
total
=
0
batchParseResult
.
valid
=
0
batchParseResult
.
valid
=
0
...
@@ -1173,14 +1248,18 @@ const handleEdit = (proxy: Proxy) => {
...
@@ -1173,14 +1248,18 @@ const handleEdit = (proxy: Proxy) => {
editForm
.
host
=
proxy
.
host
editForm
.
host
=
proxy
.
host
editForm
.
port
=
proxy
.
port
editForm
.
port
=
proxy
.
port
editForm
.
username
=
proxy
.
username
||
''
editForm
.
username
=
proxy
.
username
||
''
editForm
.
password
=
''
editForm
.
password
=
proxy
.
password
||
''
editForm
.
status
=
proxy
.
status
editForm
.
status
=
proxy
.
status
editPasswordVisible
.
value
=
false
editPasswordDirty
.
value
=
false
showEditModal
.
value
=
true
showEditModal
.
value
=
true
}
}
const
closeEditModal
=
()
=>
{
const
closeEditModal
=
()
=>
{
showEditModal
.
value
=
false
showEditModal
.
value
=
false
editingProxy
.
value
=
null
editingProxy
.
value
=
null
editPasswordVisible
.
value
=
false
editPasswordDirty
.
value
=
false
}
}
const
handleUpdateProxy
=
async
()
=>
{
const
handleUpdateProxy
=
async
()
=>
{
...
@@ -1209,10 +1288,9 @@ const handleUpdateProxy = async () => {
...
@@ -1209,10 +1288,9 @@ const handleUpdateProxy = async () => {
status
:
editForm
.
status
status
:
editForm
.
status
}
}
// Only include password if it was changed
// Only include password if user actually modified the field
const
trimmedPassword
=
editForm
.
password
.
trim
()
if
(
editPasswordDirty
.
value
)
{
if
(
trimmedPassword
)
{
updateData
.
password
=
editForm
.
password
.
trim
()
||
null
updateData
.
password
=
trimmedPassword
}
}
await
adminAPI
.
proxies
.
update
(
editingProxy
.
value
.
id
,
updateData
)
await
adminAPI
.
proxies
.
update
(
editingProxy
.
value
.
id
,
updateData
)
...
@@ -1715,12 +1793,60 @@ const closeAccountsModal = () => {
...
@@ -1715,12 +1793,60 @@ const closeAccountsModal = () => {
proxyAccounts
.
value
=
[]
proxyAccounts
.
value
=
[]
}
}
// ── Proxy URL copy ──
function
buildAuthPart
(
row
:
any
):
string
{
const
user
=
row
.
username
?
encodeURIComponent
(
row
.
username
)
:
''
const
pass
=
row
.
password
?
encodeURIComponent
(
row
.
password
)
:
''
if
(
user
&&
pass
)
return
`${user
}
:${pass
}
@`
if
(
user
)
return
`${user
}
@`
if
(
pass
)
return
`:${pass
}
@`
return
''
}
function
buildProxyUrl
(
row
:
any
):
string
{
return
`${row.protocol
}
://${buildAuthPart(row)
}
${row.host
}
:${row.port
}
`
}
function
getCopyFormats
(
row
:
any
)
{
const
hasAuth
=
row
.
username
||
row
.
password
const
fullUrl
=
buildProxyUrl
(
row
)
const
formats
=
[
{
label
:
fullUrl
,
value
:
fullUrl
}
,
]
if
(
hasAuth
)
{
const
withoutProtocol
=
fullUrl
.
replace
(
/^
[^
:
]
+:
\/\/
/
,
''
)
formats
.
push
({
label
:
withoutProtocol
,
value
:
withoutProtocol
}
)
}
formats
.
push
({
label
:
`${row.host
}
:${row.port
}
`
,
value
:
`${row.host
}
:${row.port
}
`
}
)
return
formats
}
function
copyProxyUrl
(
row
:
any
)
{
copyToClipboard
(
buildProxyUrl
(
row
),
t
(
'
admin.proxies.urlCopied
'
))
copyMenuProxyId
.
value
=
null
}
function
toggleCopyMenu
(
id
:
number
)
{
copyMenuProxyId
.
value
=
copyMenuProxyId
.
value
===
id
?
null
:
id
}
function
copyFormat
(
value
:
string
)
{
copyToClipboard
(
value
,
t
(
'
admin.proxies.urlCopied
'
))
copyMenuProxyId
.
value
=
null
}
function
closeCopyMenu
()
{
copyMenuProxyId
.
value
=
null
}
onMounted
(()
=>
{
onMounted
(()
=>
{
loadProxies
()
loadProxies
()
document
.
addEventListener
(
'
click
'
,
closeCopyMenu
)
}
)
}
)
onUnmounted
(()
=>
{
onUnmounted
(()
=>
{
clearTimeout
(
searchTimeout
)
clearTimeout
(
searchTimeout
)
abortController
?.
abort
()
abortController
?.
abort
()
document
.
removeEventListener
(
'
click
'
,
closeCopyMenu
)
}
)
}
)
<
/script
>
<
/script
>
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