@@ -813,15 +879,18 @@ import ImportDataModal from '@/components/admin/proxy/ImportDataModal.vue'
import Select from '@/components/common/Select.vue'
import Icon from '@/components/icons/Icon.vue'
import PlatformTypeBadge from '@/components/common/PlatformTypeBadge.vue'
+import { useClipboard } from '@/composables/useClipboard'
const { t } = useI18n()
const appStore = useAppStore()
+const { copyToClipboard } = useClipboard()
const columns = computed
(() => [
{ key: 'select', label: '', sortable: false },
{ key: 'name', label: t('admin.proxies.columns.name'), sortable: true },
{ key: 'protocol', label: t('admin.proxies.columns.protocol'), sortable: true },
{ 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: 'account_count', label: t('admin.proxies.columns.accounts'), sortable: true },
{ key: 'latency', label: t('admin.proxies.columns.latency'), sortable: false },
@@ -858,6 +927,8 @@ const editStatusOptions = computed(() => [
])
const proxies = ref([])
+const visiblePasswordIds = reactive(new Set())
+const copyMenuProxyId = ref(null)
const loading = ref(false)
const searchQuery = ref('')
const filters = reactive({
@@ -872,7 +943,10 @@ const pagination = reactive({
})
const showCreateModal = ref(false)
+const createPasswordVisible = ref(false)
const showEditModal = ref(false)
+const editPasswordVisible = ref(false)
+const editPasswordDirty = ref(false)
const showImportData = ref(false)
const showDeleteDialog = ref(false)
const showBatchDeleteDialog = ref(false)
@@ -1030,6 +1104,7 @@ const closeCreateModal = () => {
createForm.port = 8080
createForm.username = ''
createForm.password = ''
+ createPasswordVisible.value = false
batchInput.value = ''
batchParseResult.total = 0
batchParseResult.valid = 0
@@ -1173,14 +1248,18 @@ const handleEdit = (proxy: Proxy) => {
editForm.host = proxy.host
editForm.port = proxy.port
editForm.username = proxy.username || ''
- editForm.password = ''
+ editForm.password = proxy.password || ''
editForm.status = proxy.status
+ editPasswordVisible.value = false
+ editPasswordDirty.value = false
showEditModal.value = true
}
const closeEditModal = () => {
showEditModal.value = false
editingProxy.value = null
+ editPasswordVisible.value = false
+ editPasswordDirty.value = false
}
const handleUpdateProxy = async () => {
@@ -1209,10 +1288,9 @@ const handleUpdateProxy = async () => {
status: editForm.status
}
- // Only include password if it was changed
- const trimmedPassword = editForm.password.trim()
- if (trimmedPassword) {
- updateData.password = trimmedPassword
+ // Only include password if user actually modified the field
+ if (editPasswordDirty.value) {
+ updateData.password = editForm.password.trim() || null
}
await adminAPI.proxies.update(editingProxy.value.id, updateData)
@@ -1715,12 +1793,60 @@ const closeAccountsModal = () => {
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(() => {
loadProxies()
+ document.addEventListener('click', closeCopyMenu)
})
onUnmounted(() => {
clearTimeout(searchTimeout)
abortController?.abort()
+ document.removeEventListener('click', closeCopyMenu)
})