804 lines
28 KiB
JavaScript
804 lines
28 KiB
JavaScript
// 代理管理页面JavaScript
|
||
|
||
class ProxyManager {
|
||
constructor() {
|
||
this.currentPage = 1;
|
||
this.pageSize = 20;
|
||
this.totalCount = 0;
|
||
this.proxies = [];
|
||
this.selectedProxies = new Set();
|
||
this.searchParams = {
|
||
ip: '',
|
||
port: '',
|
||
location: '',
|
||
status: '',
|
||
sortBy: 'created_at',
|
||
order: 'DESC'
|
||
};
|
||
|
||
this.init();
|
||
}
|
||
|
||
async init() {
|
||
console.log('初始化代理管理页面...');
|
||
|
||
// 绑定事件
|
||
this.bindEvents();
|
||
|
||
// 加载代理列表
|
||
await this.loadProxies();
|
||
|
||
console.log('代理管理页面初始化完成');
|
||
}
|
||
|
||
bindEvents() {
|
||
// 搜索表单
|
||
document.getElementById('searchForm').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
this.searchParams.ip = document.getElementById('searchIp').value;
|
||
this.searchParams.port = document.getElementById('searchPort').value;
|
||
this.searchParams.location = document.getElementById('searchLocation').value;
|
||
this.searchParams.status = document.getElementById('filterStatus').value;
|
||
this.searchParams.sortBy = document.getElementById('sortBy').value;
|
||
this.currentPage = 1;
|
||
this.loadProxies();
|
||
});
|
||
|
||
// 单个代理验证
|
||
document.getElementById('validateSingleProxy').addEventListener('click', () => {
|
||
const proxyId = document.getElementById('validateSingleProxy').dataset.proxyId;
|
||
if (proxyId) {
|
||
this.validateSingleProxy(proxyId);
|
||
}
|
||
});
|
||
}
|
||
|
||
async loadProxies() {
|
||
try {
|
||
this.showLoading(true);
|
||
|
||
// 构建查询参数
|
||
const params = new URLSearchParams({
|
||
limit: this.pageSize,
|
||
offset: (this.currentPage - 1) * this.pageSize,
|
||
sortBy: this.searchParams.sortBy,
|
||
order: this.searchParams.order
|
||
});
|
||
|
||
if (this.searchParams.ip) params.append('ip', this.searchParams.ip);
|
||
if (this.searchParams.port) params.append('port', this.searchParams.port);
|
||
if (this.searchParams.location) params.append('location', this.searchParams.location);
|
||
if (this.searchParams.status !== '') params.append('validOnly', this.searchParams.status === '1');
|
||
|
||
const response = await fetch(`/api/proxies?${params}`);
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.proxies = result.data;
|
||
this.totalCount = result.pagination.total;
|
||
this.renderProxyTable();
|
||
this.renderPagination();
|
||
this.updateStatistics();
|
||
} else {
|
||
this.showAlert('加载代理列表失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载代理列表失败:', error);
|
||
this.showAlert('加载代理列表失败: ' + error.message, 'danger');
|
||
} finally {
|
||
this.showLoading(false);
|
||
}
|
||
}
|
||
|
||
renderProxyTable() {
|
||
const tbody = document.getElementById('proxyTableBody');
|
||
|
||
if (this.proxies.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr>
|
||
<td colspan="9" class="text-center p-4">
|
||
<i class="bi bi-inbox fs-1 text-muted"></i>
|
||
<p class="text-muted mt-2">暂无代理数据</p>
|
||
<button class="btn btn-primary" onclick="proxyManager.startScrape()">
|
||
<i class="bi bi-download"></i> 立即抓取代理
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = this.proxies.map(proxy => `
|
||
<tr>
|
||
<td>
|
||
<input type="checkbox" class="form-check-input proxy-checkbox"
|
||
value="${proxy.id}" onchange="proxyManager.toggleProxySelection(${proxy.id})">
|
||
</td>
|
||
<td>
|
||
<code>${proxy.ip}</code>
|
||
</td>
|
||
<td>${proxy.port}</td>
|
||
<td>
|
||
<small class="text-muted">${proxy.location || '-'}</small>
|
||
</td>
|
||
<td>
|
||
<span class="badge ${proxy.is_valid ? 'bg-success' : 'bg-danger'}">
|
||
<i class="bi bi-${proxy.is_valid ? 'check-circle' : 'x-circle'}"></i>
|
||
${proxy.is_valid ? '可用' : '不可用'}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">
|
||
${proxy.response_time ? proxy.response_time + 'ms' : '-'}
|
||
</small>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">
|
||
${proxy.last_check_time ? this.formatDateTime(proxy.last_check_time) : '-'}
|
||
</small>
|
||
</td>
|
||
<td>
|
||
<small class="text-muted">
|
||
${this.formatDateTime(proxy.created_at)}
|
||
</small>
|
||
</td>
|
||
<td>
|
||
<div class="btn-group btn-group-sm">
|
||
<button class="btn btn-outline-primary" onclick="proxyManager.showProxyDetail(${proxy.id})" title="查看详情">
|
||
<i class="bi bi-eye"></i>
|
||
</button>
|
||
<button class="btn btn-outline-success" onclick="proxyManager.validateSingleProxy(${proxy.id})" title="验证">
|
||
<i class="bi bi-check2"></i>
|
||
</button>
|
||
<button class="btn btn-outline-danger" onclick="proxyManager.deleteProxy(${proxy.id})" title="删除">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
renderPagination() {
|
||
const pagination = document.getElementById('pagination');
|
||
const totalPages = Math.ceil(this.totalCount / this.pageSize);
|
||
|
||
if (totalPages <= 1) {
|
||
pagination.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
let paginationHTML = '';
|
||
|
||
// 上一页
|
||
paginationHTML += `
|
||
<li class="page-item ${this.currentPage === 1 ? 'disabled' : ''}">
|
||
<a class="page-link" href="#" onclick="proxyManager.goToPage(${this.currentPage - 1}); return false;">
|
||
<i class="bi bi-chevron-left"></i>
|
||
</a>
|
||
</li>
|
||
`;
|
||
|
||
// 页码
|
||
const startPage = Math.max(1, this.currentPage - 2);
|
||
const endPage = Math.min(totalPages, this.currentPage + 2);
|
||
|
||
if (startPage > 1) {
|
||
paginationHTML += `
|
||
<li class="page-item">
|
||
<a class="page-link" href="#" onclick="proxyManager.goToPage(1); return false;">1</a>
|
||
</li>
|
||
`;
|
||
if (startPage > 2) {
|
||
paginationHTML += '<li class="page-item disabled"><a class="page-link" href="#">...</a></li>';
|
||
}
|
||
}
|
||
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
paginationHTML += `
|
||
<li class="page-item ${i === this.currentPage ? 'active' : ''}">
|
||
<a class="page-link" href="#" onclick="proxyManager.goToPage(${i}); return false;">${i}</a>
|
||
</li>
|
||
`;
|
||
}
|
||
|
||
if (endPage < totalPages) {
|
||
if (endPage < totalPages - 1) {
|
||
paginationHTML += '<li class="page-item disabled"><a class="page-link" href="#">...</a></li>';
|
||
}
|
||
paginationHTML += `
|
||
<li class="page-item">
|
||
<a class="page-link" href="#" onclick="proxyManager.goToPage(${totalPages}); return false;">${totalPages}</a>
|
||
</li>
|
||
`;
|
||
}
|
||
|
||
// 下一页
|
||
paginationHTML += `
|
||
<li class="page-item ${this.currentPage === totalPages ? 'disabled' : ''}">
|
||
<a class="page-link" href="#" onclick="proxyManager.goToPage(${this.currentPage + 1}); return false;">
|
||
<i class="bi bi-chevron-right"></i>
|
||
</a>
|
||
</li>
|
||
`;
|
||
|
||
pagination.innerHTML = paginationHTML;
|
||
}
|
||
|
||
updateStatistics() {
|
||
document.getElementById('totalCount').textContent = this.totalCount;
|
||
document.getElementById('validCount').textContent = this.proxies.filter(p => p.is_valid).length;
|
||
document.getElementById('invalidCount').textContent = this.proxies.filter(p => !p.is_valid).length;
|
||
document.getElementById('showingCount').textContent = this.proxies.length;
|
||
}
|
||
|
||
async showProxyDetail(proxyId) {
|
||
try {
|
||
const proxy = this.proxies.find(p => p.id === proxyId);
|
||
if (!proxy) return;
|
||
|
||
const content = document.getElementById('proxyDetailContent');
|
||
content.innerHTML = `
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<strong>IP地址:</strong><br>
|
||
<code>${proxy.ip}</code>
|
||
</div>
|
||
<div class="col-6">
|
||
<strong>端口:</strong><br>
|
||
${proxy.port}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<strong>位置:</strong><br>
|
||
${proxy.location || '-'}
|
||
</div>
|
||
<div class="col-6">
|
||
<strong>状态:</strong><br>
|
||
<span class="badge ${proxy.is_valid ? 'bg-success' : 'bg-danger'}">
|
||
${proxy.is_valid ? '可用' : '不可用'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<strong>响应时间:</strong><br>
|
||
${proxy.response_time ? proxy.response_time + 'ms' : '-'}
|
||
</div>
|
||
<div class="col-6">
|
||
<strong>最后验证:</strong><br>
|
||
${proxy.last_check_time ? this.formatDateTime(proxy.last_check_time) : '-'}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<strong>创建时间:</strong><br>
|
||
${this.formatDateTime(proxy.created_at)}
|
||
</div>
|
||
<div class="col-6">
|
||
<strong>更新时间:</strong><br>
|
||
${this.formatDateTime(proxy.updated_at)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 设置验证按钮的数据属性
|
||
document.getElementById('validateSingleProxy').dataset.proxyId = proxyId;
|
||
|
||
// 显示模态框
|
||
const modal = new bootstrap.Modal(document.getElementById('proxyDetailModal'));
|
||
modal.show();
|
||
} catch (error) {
|
||
console.error('显示代理详情失败:', error);
|
||
this.showAlert('显示代理详情失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async validateSingleProxy(proxyId) {
|
||
try {
|
||
const proxy = this.proxies.find(p => p.id === proxyId);
|
||
if (!proxy) return;
|
||
|
||
// 关闭详情模态框
|
||
const detailModal = bootstrap.Modal.getInstance(document.getElementById('proxyDetailModal'));
|
||
if (detailModal) detailModal.hide();
|
||
|
||
// 显示验证进度
|
||
const modal = new bootstrap.Modal(document.getElementById('validationModal'));
|
||
document.getElementById('validationProgress').style.display = 'block';
|
||
document.getElementById('validationResults').innerHTML = '';
|
||
document.getElementById('validationStatus').textContent = `正在验证 ${proxy.ip}:${proxy.port}...`;
|
||
document.getElementById('validationProgressBar').style.width = '50%';
|
||
modal.show();
|
||
|
||
const response = await fetch('/api/proxies/verify', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
ip: proxy.ip,
|
||
port: proxy.port
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
// 更新进度条
|
||
document.getElementById('validationProgressBar').style.width = '100%';
|
||
document.getElementById('validationStatus').textContent = '验证完成';
|
||
|
||
// 显示结果
|
||
let responseResultHtml = '';
|
||
|
||
if (result.data.responseStatus !== null && result.data.responseStatus !== undefined) {
|
||
responseResultHtml = `
|
||
<hr>
|
||
<h6>响应结果:</h6>
|
||
<p><strong>状态码:</strong> <span class="badge ${result.data.responseStatus >= 200 && result.data.responseStatus < 300 ? 'bg-success' : 'bg-warning'}">${result.data.responseStatus}</span></p>
|
||
`;
|
||
|
||
if (result.data.responseData) {
|
||
// 限制显示长度,避免内容过长
|
||
let displayData = result.data.responseData;
|
||
const maxLength = 300;
|
||
if (displayData.length > maxLength) {
|
||
displayData = displayData.substring(0, maxLength) + '... (内容已截断,完整内容请查看日志)';
|
||
}
|
||
|
||
responseResultHtml += `
|
||
<p><strong>响应内容:</strong></p>
|
||
<pre class="bg-light p-2 border rounded" style="max-height: 200px; overflow-y: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${this.escapeHtml(displayData)}</pre>
|
||
`;
|
||
}
|
||
|
||
if (result.data.testUrl) {
|
||
responseResultHtml += `
|
||
<p class="text-muted small"><strong>测试URL:</strong> ${result.data.testUrl}</p>
|
||
`;
|
||
}
|
||
} else if (result.data.error) {
|
||
responseResultHtml = `
|
||
<hr>
|
||
<h6>响应结果:</h6>
|
||
<p class="text-danger"><strong>错误:</strong> ${result.data.error}</p>
|
||
`;
|
||
}
|
||
|
||
document.getElementById('validationResults').innerHTML = `
|
||
<div class="alert ${result.data.isValid ? 'alert-success' : 'alert-danger'}">
|
||
<h6>验证结果: ${result.data.isValid ? '成功' : '失败'}</h6>
|
||
<p><strong>IP:</strong> ${result.data.ip}:${result.data.port}</p>
|
||
<p><strong>响应时间:</strong> ${result.data.responseTime}ms</p>
|
||
${result.data.error ? `<p><strong>错误信息:</strong> ${result.data.error}</p>` : ''}
|
||
</div>
|
||
${responseResultHtml}
|
||
`;
|
||
|
||
// 延迟刷新列表
|
||
setTimeout(() => {
|
||
this.loadProxies();
|
||
}, 1000);
|
||
|
||
} catch (error) {
|
||
console.error('验证代理失败:', error);
|
||
this.showAlert('验证代理失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async deleteProxy(proxyId) {
|
||
if (!confirm('确定要删除这个代理吗?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const proxy = this.proxies.find(p => p.id === proxyId);
|
||
if (!proxy) return;
|
||
|
||
// 这里应该调用删除API,但目前我们的API没有单个删除功能
|
||
// 暂时显示提示
|
||
this.showAlert(`代理 ${proxy.ip}:${proxy.port} 删除功能待实现`, 'info');
|
||
|
||
} catch (error) {
|
||
console.error('删除代理失败:', error);
|
||
this.showAlert('删除代理失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
toggleProxySelection(proxyId) {
|
||
if (this.selectedProxies.has(proxyId)) {
|
||
this.selectedProxies.delete(proxyId);
|
||
} else {
|
||
this.selectedProxies.add(proxyId);
|
||
}
|
||
this.updateSelectAllCheckbox();
|
||
}
|
||
|
||
toggleSelectAll() {
|
||
const selectAll = document.getElementById('selectAll').checked;
|
||
const checkboxes = document.querySelectorAll('.proxy-checkbox');
|
||
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.checked = selectAll;
|
||
const proxyId = parseInt(checkbox.value);
|
||
if (selectAll) {
|
||
this.selectedProxies.add(proxyId);
|
||
} else {
|
||
this.selectedProxies.delete(proxyId);
|
||
}
|
||
});
|
||
}
|
||
|
||
updateSelectAllCheckbox() {
|
||
const checkboxes = document.querySelectorAll('.proxy-checkbox');
|
||
const selectAll = document.getElementById('selectAll');
|
||
selectAll.checked = checkboxes.length > 0 && this.selectedProxies.size === checkboxes.length;
|
||
}
|
||
|
||
goToPage(page) {
|
||
this.currentPage = page;
|
||
this.loadProxies();
|
||
}
|
||
|
||
async validateAll() {
|
||
if (this.selectedProxies.size === 0) {
|
||
this.showAlert('请先选择要验证的代理', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const selectedProxiesList = Array.from(this.selectedProxies).map(id =>
|
||
this.proxies.find(p => p.id === id)
|
||
).filter(p => p);
|
||
|
||
// 显示验证进度
|
||
const modal = new bootstrap.Modal(document.getElementById('validationModal'));
|
||
document.getElementById('validationProgress').style.display = 'block';
|
||
document.getElementById('validationResults').innerHTML = '';
|
||
document.getElementById('validationProgressBar').style.width = '0%';
|
||
modal.show();
|
||
|
||
const response = await fetch('/api/proxies/verify-batch', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
proxies: selectedProxiesList
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
// 更新进度条
|
||
document.getElementById('validationProgressBar').style.width = '100%';
|
||
document.getElementById('validationStatus').textContent = '验证完成';
|
||
|
||
// 显示结果
|
||
document.getElementById('validationResults').innerHTML = `
|
||
<div class="alert alert-info">
|
||
<h6>批量验证完成</h6>
|
||
<p><strong>总验证数:</strong> ${result.data.validated}</p>
|
||
<p><strong>有效代理:</strong> <span class="badge bg-success">${result.data.valid}</span></p>
|
||
<p><strong>无效代理:</strong> <span class="badge bg-danger">${result.data.invalid}</span></p>
|
||
</div>
|
||
`;
|
||
|
||
// 延迟刷新列表
|
||
setTimeout(() => {
|
||
this.loadProxies();
|
||
}, 1000);
|
||
|
||
} catch (error) {
|
||
console.error('批量验证失败:', error);
|
||
this.showAlert('批量验证失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async cleanupInvalid() {
|
||
if (!confirm('确定要清理所有无效代理吗?此操作不可恢复。')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/proxies/cleanup', {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.showAlert(result.message, 'success');
|
||
await this.loadProxies();
|
||
} else {
|
||
this.showAlert('清理失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('清理无效代理失败:', error);
|
||
this.showAlert('清理无效代理失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async exportProxies() {
|
||
try {
|
||
const response = await fetch('/api/proxies?limit=1000&validOnly=true');
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
const csv = this.convertToCSV(result.data);
|
||
this.downloadCSV(csv, `valid_proxies_${new Date().toISOString().slice(0, 10)}.csv`);
|
||
this.showAlert('数据导出成功', 'success');
|
||
} else {
|
||
this.showAlert('导出数据失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('导出数据失败:', error);
|
||
this.showAlert('导出数据失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
async startScrape() {
|
||
try {
|
||
const response = await fetch('/api/dashboard/actions/scrape', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ pages: 40 })
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
this.showAlert('抓取任务已启动', 'success');
|
||
// 延迟刷新数据
|
||
setTimeout(() => this.loadProxies(), 2000);
|
||
} else {
|
||
this.showAlert('启动抓取任务失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('启动抓取任务失败:', error);
|
||
this.showAlert('启动抓取任务失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
|
||
// 工具函数
|
||
formatDateTime(dateString) {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleString('zh-CN', {
|
||
month: 'short',
|
||
day: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
|
||
showLoading(show) {
|
||
const spinner = document.querySelector('#proxyTableBody .spinner-border');
|
||
if (spinner) {
|
||
spinner.parentElement.style.display = show ? 'block' : 'none';
|
||
}
|
||
}
|
||
|
||
showAlert(message, type = 'info') {
|
||
const alertContainer = document.getElementById('alertContainer');
|
||
if (!alertContainer) return;
|
||
|
||
const alertId = 'alert-' + Date.now();
|
||
const alertHtml = `
|
||
<div class="alert alert-${type} alert-dismissible fade show" role="alert" id="${alertId}">
|
||
<i class="bi bi-${this.getAlertIcon(type)}"></i>
|
||
${message}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
</div>
|
||
`;
|
||
|
||
alertContainer.insertAdjacentHTML('beforeend', alertHtml);
|
||
|
||
// 自动移除提示
|
||
setTimeout(() => {
|
||
const alertElement = document.getElementById(alertId);
|
||
if (alertElement) {
|
||
alertElement.remove();
|
||
}
|
||
}, 5000);
|
||
}
|
||
|
||
getAlertIcon(type) {
|
||
const icons = {
|
||
'success': 'check-circle-fill',
|
||
'danger': 'exclamation-triangle-fill',
|
||
'warning': 'exclamation-triangle-fill',
|
||
'info': 'info-circle-fill'
|
||
};
|
||
return icons[type] || 'info-circle-fill';
|
||
}
|
||
|
||
escapeHtml(text) {
|
||
if (!text) return '';
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
convertToCSV(data) {
|
||
if (!data || data.length === 0) return '';
|
||
|
||
const headers = ['IP', '端口', '位置', '响应时间', '最后验证时间', '创建时间'];
|
||
const csvRows = [headers.join(',')];
|
||
|
||
data.forEach(proxy => {
|
||
const row = [
|
||
proxy.ip,
|
||
proxy.port,
|
||
`"${proxy.location || ''}"`,
|
||
proxy.response_time || '',
|
||
proxy.last_check_time || '',
|
||
proxy.created_at
|
||
];
|
||
csvRows.push(row.join(','));
|
||
});
|
||
|
||
return csvRows.join('\n');
|
||
}
|
||
|
||
downloadCSV(csv, filename) {
|
||
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
link.setAttribute('href', url);
|
||
link.setAttribute('download', filename);
|
||
link.style.visibility = 'hidden';
|
||
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
// 导入代理功能
|
||
async importProxies(proxies) {
|
||
try {
|
||
this.showAlert('开始导入并验证代理...', 'info');
|
||
|
||
const response = await fetch('/api/proxies/import', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ proxies })
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
const { data } = result;
|
||
let message = `导入完成!`;
|
||
message += `\n📊 总数: ${data.total}`;
|
||
message += `\n✅ 格式有效: ${data.format_valid}`;
|
||
message += `\n❌ 格式无效: ${data.format_invalid}`;
|
||
message += `\n🔍 验证通过: ${data.valid}`;
|
||
message += `\n❌ 验证失败: ${data.invalid}`;
|
||
message += `\n💾 保存成功: ${data.saved}`;
|
||
|
||
this.showAlert(message, 'success');
|
||
|
||
// 刷新代理列表
|
||
setTimeout(() => this.loadProxies(), 2000);
|
||
} else {
|
||
this.showAlert('导入失败: ' + result.error, 'danger');
|
||
}
|
||
} catch (error) {
|
||
console.error('导入代理失败:', error);
|
||
this.showAlert('导入代理失败: ' + error.message, 'danger');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 全局函数(供HTML调用)
|
||
let proxyManager;
|
||
|
||
async function refreshProxies() {
|
||
if (proxyManager) {
|
||
await proxyManager.loadProxies();
|
||
proxyManager.showAlert('代理列表已刷新', 'info');
|
||
}
|
||
}
|
||
|
||
async function validateAll() {
|
||
if (proxyManager) {
|
||
await proxyManager.validateAll();
|
||
}
|
||
}
|
||
|
||
async function cleanupInvalid() {
|
||
if (proxyManager) {
|
||
await proxyManager.cleanupInvalid();
|
||
}
|
||
}
|
||
|
||
async function exportProxies() {
|
||
if (proxyManager) {
|
||
await proxyManager.exportProxies();
|
||
}
|
||
}
|
||
|
||
// 显示导入模态框
|
||
function showImportModal() {
|
||
const modal = new bootstrap.Modal(document.getElementById('importModal'));
|
||
modal.show();
|
||
}
|
||
|
||
// 从模态框导入代理
|
||
async function importProxiesFromModal() {
|
||
const jsonInput = document.getElementById('proxyJsonInput').value.trim();
|
||
|
||
if (!jsonInput) {
|
||
alert('请输入代理JSON数据');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const proxies = JSON.parse(jsonInput);
|
||
|
||
if (!Array.isArray(proxies)) {
|
||
alert('代理数据必须是数组格式');
|
||
return;
|
||
}
|
||
|
||
// 关闭模态框
|
||
const modal = bootstrap.Modal.getInstance(document.getElementById('importModal'));
|
||
modal.hide();
|
||
|
||
// 清空输入框
|
||
document.getElementById('proxyJsonInput').value = '';
|
||
|
||
// 开始导入
|
||
if (proxyManager) {
|
||
await proxyManager.importProxies(proxies);
|
||
}
|
||
} catch (error) {
|
||
alert('JSON格式错误: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 处理文件导入
|
||
async function handleImportFile(event) {
|
||
const file = event.target.files[0];
|
||
|
||
if (!file) return;
|
||
|
||
if (!file.name.endsWith('.json')) {
|
||
alert('请选择JSON文件');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const text = await file.text();
|
||
const proxies = JSON.parse(text);
|
||
|
||
if (!Array.isArray(proxies)) {
|
||
alert('文件内容必须是代理数组');
|
||
return;
|
||
}
|
||
|
||
// 清空文件输入
|
||
event.target.value = '';
|
||
|
||
// 开始导入
|
||
if (proxyManager) {
|
||
await proxyManager.importProxies(proxies);
|
||
}
|
||
} catch (error) {
|
||
alert('文件读取失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
proxyManager = new ProxyManager();
|
||
}); |