功能完善
This commit is contained in:
parent
5d61a240ab
commit
eda8e0bfcb
3
app/.env
3
app/.env
@ -15,3 +15,6 @@ REDIS_URL=redis://127.0.0.1:6379/5
|
||||
|
||||
# 日志级别
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# 端口数==条数 从172.30.168.2开始
|
||||
PORT_NUM=3
|
||||
@ -15,18 +15,26 @@ class Settings(BaseModel):
|
||||
eip_default_num: int = int(os.getenv("EIP_DEFAULT_NUM", "10"))
|
||||
redis_url: str = os.getenv("REDIS_URL", "redis://localhost:6379/0")
|
||||
log_level: str = os.getenv("LOG_LEVEL", "INFO")
|
||||
port_num: int = int(os.getenv("PORT_NUM", 3))
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
client_infos={}
|
||||
|
||||
for i in range(230):
|
||||
for i in range(settings.port_num):
|
||||
ip4 = 2+i
|
||||
ip = '172.30.168.'+str(ip4)
|
||||
client_infos[str(i+1)] = ip
|
||||
|
||||
|
||||
port_mapping = {}
|
||||
for i in range(settings.port_num):
|
||||
num = i+1
|
||||
port = 44000 + num
|
||||
port_mapping[num] = port
|
||||
|
||||
|
||||
city_dict = {
|
||||
"上海": {
|
||||
"上海": "f8a5e9b04178490cf8e71f5b273538aa75ef2c978ecac974a176d93af966ef53"
|
||||
|
||||
174
app/line_status_manager.py
Normal file
174
app/line_status_manager.py
Normal file
@ -0,0 +1,174 @@
|
||||
"""
|
||||
线路状态管理模块
|
||||
|
||||
负责保存和读取每个线路的最新状态信息到文件中
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any
|
||||
from pathlib import Path
|
||||
from .config import client_infos
|
||||
|
||||
class LineStatusManager:
|
||||
"""线路状态管理器"""
|
||||
|
||||
def __init__(self, status_file: str = "line_status.json"):
|
||||
"""
|
||||
初始化状态管理器
|
||||
|
||||
Args:
|
||||
status_file: 状态文件路径
|
||||
"""
|
||||
self.status_file = Path(status_file)
|
||||
self._ensure_status_file()
|
||||
|
||||
def _ensure_status_file(self):
|
||||
"""确保状态文件存在"""
|
||||
if not self.status_file.exists():
|
||||
self._save_status({})
|
||||
|
||||
def _load_status(self) -> Dict[str, Any]:
|
||||
"""从文件加载状态"""
|
||||
try:
|
||||
with open(self.status_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
def _save_status(self, status: Dict[str, Any]):
|
||||
"""保存状态到文件"""
|
||||
with open(self.status_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(status, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def get_line_status(self, line_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
获取指定线路的状态
|
||||
|
||||
Args:
|
||||
line_id: 线路ID
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 线路状态信息
|
||||
"""
|
||||
status = self._load_status()
|
||||
line_key = str(line_id)
|
||||
|
||||
if line_key not in status:
|
||||
return {
|
||||
"id": line_id,
|
||||
"current_ip": None,
|
||||
"last_rotate_time": None,
|
||||
"status": "inactive",
|
||||
"edge_device": None,
|
||||
"geo_location": None,
|
||||
"rotate_count": 0
|
||||
}
|
||||
|
||||
return status[line_key]
|
||||
|
||||
def update_line_status(self, line_id: int, **kwargs):
|
||||
"""
|
||||
更新线路状态
|
||||
|
||||
Args:
|
||||
line_id: 线路ID
|
||||
**kwargs: 要更新的状态字段
|
||||
"""
|
||||
status = self._load_status()
|
||||
line_key = str(line_id)
|
||||
|
||||
if line_key not in status:
|
||||
status[line_key] = {
|
||||
"id": line_id,
|
||||
"current_ip": None,
|
||||
"last_rotate_time": None,
|
||||
"status": "inactive",
|
||||
"edge_device": None,
|
||||
"geo_location": None,
|
||||
"rotate_count": 0
|
||||
}
|
||||
|
||||
# 更新字段
|
||||
for key, value in kwargs.items():
|
||||
status[line_key][key] = value
|
||||
|
||||
# 更新时间戳
|
||||
status[line_key]["last_update_time"] = datetime.now().isoformat()
|
||||
|
||||
self._save_status(status)
|
||||
|
||||
def get_all_lines_status(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有线路状态(仅已保存的状态)
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 所有线路状态列表,按线路ID排序
|
||||
"""
|
||||
status = self._load_status()
|
||||
lines = list(status.values())
|
||||
|
||||
# 按线路ID排序
|
||||
lines.sort(key=lambda x: x.get("id", 0))
|
||||
|
||||
return lines
|
||||
|
||||
def get_all_lines_with_defaults(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有线路状态,包括默认配置的线路
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 所有线路状态列表,包括未配置的线路
|
||||
"""
|
||||
|
||||
|
||||
# 获取已保存的状态
|
||||
saved_status = self._load_status()
|
||||
lines = []
|
||||
|
||||
# 遍历所有配置的线路
|
||||
for port_id, default_ip in client_infos.items():
|
||||
line_id = int(port_id)
|
||||
line_key = str(line_id)
|
||||
|
||||
if line_key in saved_status:
|
||||
# 使用已保存的状态
|
||||
line_data = saved_status[line_key].copy()
|
||||
else:
|
||||
# 使用默认状态
|
||||
line_data = {
|
||||
"id": line_id,
|
||||
"current_ip": default_ip,
|
||||
"last_rotate_time": None,
|
||||
"status": "inactive",
|
||||
"edge_device": None,
|
||||
"geo_location": None,
|
||||
"rotate_count": 0
|
||||
}
|
||||
|
||||
lines.append(line_data)
|
||||
|
||||
# 按线路ID排序
|
||||
lines.sort(key=lambda x: x.get("id", 0))
|
||||
|
||||
return lines
|
||||
|
||||
def increment_rotate_count(self, line_id: int):
|
||||
"""增加轮换次数计数"""
|
||||
current_status = self.get_line_status(line_id)
|
||||
self.update_line_status(
|
||||
line_id,
|
||||
rotate_count=current_status.get("rotate_count", 0) + 1
|
||||
)
|
||||
|
||||
|
||||
# 创建全局状态管理器实例
|
||||
status_manager = LineStatusManager()
|
||||
|
||||
# 使用示例:
|
||||
# 获取所有线路状态(仅已保存的)
|
||||
# all_lines = status_manager.get_all_lines_status()
|
||||
#
|
||||
# 获取所有线路状态(包括默认配置)
|
||||
# all_lines_with_defaults = status_manager.get_all_lines_with_defaults()
|
||||
@ -12,9 +12,10 @@ IP轮换服务模块
|
||||
import random
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .config import settings, city_dict, client_infos
|
||||
from .config import settings, city_dict, client_infos, port_mapping
|
||||
from .eip_client import client_singleton as eip
|
||||
from .redis_store import store_singleton as kv
|
||||
from .line_status_manager import status_manager
|
||||
|
||||
|
||||
def _extract_device_ip_and_id(device: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
|
||||
@ -77,15 +78,29 @@ def apply_gateway_route(edge_id: Optional[str], ip: str, geo: str, client_id:str
|
||||
Returns:
|
||||
Dict[str, Any]: 网关配置设置结果
|
||||
"""
|
||||
# 创建路由规则配置
|
||||
rule = {
|
||||
"table": 1, # 路由表ID
|
||||
"enable": True, # 启用规则
|
||||
"edge": [edge_id] if edge_id else [], # 边缘设备列表
|
||||
"network": [client_infos[str(client_id)]], # 网络配置(当前为空)
|
||||
"cityhash": geo or "", # 城市哈希值
|
||||
}
|
||||
config = {"id": 1, "rules": [rule]} # 配置ID和规则列表
|
||||
lines = status_manager.get_all_lines_status()
|
||||
|
||||
rule = []
|
||||
|
||||
for line in lines:
|
||||
if line['id'] == client_id:
|
||||
rule.append({
|
||||
"table": client_id, # 路由表ID
|
||||
"enable": True, # 启用规则
|
||||
"edge": [edge_id] if edge_id else [], # 边缘设备列表
|
||||
"network": [client_infos[str(client_id)]], # 网络配置(当前为空)
|
||||
"cityhash": geo or "", # 城市哈希值
|
||||
})
|
||||
else:
|
||||
rule.append({
|
||||
"table": line['id'], # 路由表ID
|
||||
"enable": True, # 启用规则
|
||||
"edge": [line['edge_device']], # 边缘设备列表
|
||||
"network": [client_infos[str(line['id'])]], # 网络配置(当前为空)
|
||||
"cityhash": line['geo_location'], # 城市哈希值
|
||||
})
|
||||
|
||||
config = {"id": 1, "rules": rule} # 配置ID和规则列表
|
||||
return eip.gateway_config_set(settings.eip_gateway_mac, config)
|
||||
|
||||
|
||||
@ -212,4 +227,5 @@ def get_random_cityhash(cities) -> str:
|
||||
city_info = get_random_city(cities)
|
||||
return city_info["cityhash"]
|
||||
|
||||
|
||||
def get_port(line_id):
|
||||
return port_mapping[line_id]
|
||||
|
||||
@ -3,7 +3,7 @@ from typing import Optional
|
||||
from fastapi import APIRouter, Body, Query, Form,Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..rotation_service import rotate as rotate_impl, status as status_impl ,citie_list as cities_impl
|
||||
from ..rotation_service import rotate as rotate_impl, status as status_impl ,citie_list as cities_impl,get_port
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
@ -24,6 +24,22 @@ def rotate(
|
||||
# effective_cityhash = req.cityhash
|
||||
# effective_num = req.num
|
||||
result = rotate_impl(client_id=client_id,cities =cities)
|
||||
|
||||
# 如果轮换成功,更新线路状态
|
||||
if result.get("changed", False):
|
||||
from ..line_status_manager import status_manager
|
||||
from datetime import datetime
|
||||
|
||||
status_manager.update_line_status(
|
||||
client_id,
|
||||
current_ip=result.get("ip"),
|
||||
last_rotate_time=datetime.now().isoformat(),
|
||||
status="active",
|
||||
edge_device=result.get("edge"),
|
||||
geo_location=result.get("geo")
|
||||
)
|
||||
status_manager.increment_rotate_count(client_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@ -34,3 +50,27 @@ def get_status():
|
||||
@router.get("/cities")
|
||||
def get_cities():
|
||||
return cities_impl()
|
||||
|
||||
@router.get("/lines")
|
||||
def get_lines():
|
||||
"""获取所有线路信息"""
|
||||
from ..line_status_manager import status_manager
|
||||
|
||||
# 使用新的方法获取所有线路状态
|
||||
lines_data = status_manager.get_all_lines_with_defaults()
|
||||
|
||||
# 格式化返回数据
|
||||
lines = []
|
||||
for line_data in lines_data:
|
||||
lines.append({
|
||||
"id": line_data["id"],
|
||||
"port": get_port(line_data["id"]),
|
||||
"ip": line_data.get("current_ip"),
|
||||
"status": line_data.get("status", "active"),
|
||||
"last_rotate_time": line_data.get("last_rotate_time"),
|
||||
"edge_device": line_data.get("edge_device"),
|
||||
"geo_location": line_data.get("geo_location"),
|
||||
"rotate_count": line_data.get("rotate_count", 0)
|
||||
})
|
||||
|
||||
return {"data": lines}
|
||||
@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
@ -61,18 +61,6 @@
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.client-id {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #4facfe;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.client-id strong {
|
||||
color: #4facfe;
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
@ -103,26 +91,80 @@
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
.select-all-container {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background: #e3f2fd;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #2196f3;
|
||||
}
|
||||
|
||||
.select-all-container input[type="checkbox"] {
|
||||
transform: scale(1.3);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.select-all-container label {
|
||||
font-weight: 600;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.table-header h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 40px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 50px;
|
||||
padding: 8px 16px;
|
||||
font-size: 0.9em;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
@ -131,9 +173,20 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn.rotating {
|
||||
background: #6c757d;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
@ -171,22 +224,37 @@
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.select-all-container {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background: #e3f2fd;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #2196f3;
|
||||
}
|
||||
|
||||
.select-all-container input[type="checkbox"] {
|
||||
transform: scale(1.3);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.select-all-container label {
|
||||
.status-active {
|
||||
color: #28a745;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
color: #dc3545;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ip-address {
|
||||
font-family: 'Courier New', monospace;
|
||||
background: #e9ecef;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.rotate-count {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.last-rotate-time {
|
||||
font-size: 0.85em;
|
||||
color: #666;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@ -198,11 +266,6 @@
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 客户端ID显示 -->
|
||||
<div class="client-id">
|
||||
<strong>客户端ID:</strong> <span id="client-id">{{ client_id }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 省份选择 -->
|
||||
<div class="form-group">
|
||||
<label>选择省份(默认全选)</label>
|
||||
@ -215,17 +278,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="btn-container">
|
||||
<button class="btn" id="rotate-btn" onclick="performRotate()">
|
||||
🔄 开始IP轮换
|
||||
</button>
|
||||
<!-- 线路信息表格 -->
|
||||
<div class="table-container">
|
||||
<div class="table-header">
|
||||
<h2>线路信息管理</h2>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>端口号</th>
|
||||
<th>当前IP</th>
|
||||
<th>状态</th>
|
||||
<th>轮换次数</th>
|
||||
<th>最后轮换时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="lines-table-body">
|
||||
<!-- 线路数据将通过JavaScript动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在执行IP轮换,请稍候...</p>
|
||||
<p>正在加载线路信息...</p>
|
||||
</div>
|
||||
|
||||
<!-- 结果显示 -->
|
||||
@ -234,12 +313,14 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let linesData = [];
|
||||
let allProvinces = [];
|
||||
let selectedProvinces = [];
|
||||
|
||||
// 页面加载时初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadProvinces();
|
||||
loadLines();
|
||||
setupSelectAll();
|
||||
});
|
||||
|
||||
@ -299,19 +380,58 @@
|
||||
selectAllCheckbox.checked = selectedProvinces.length === allProvinces.length;
|
||||
}
|
||||
|
||||
// 执行IP轮换
|
||||
async function performRotate() {
|
||||
if (selectedProvinces.length === 0) {
|
||||
showResult('请至少选择一个省份!', 'error');
|
||||
return;
|
||||
// 加载线路列表
|
||||
async function loadLines() {
|
||||
try {
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
const response = await fetch('/proxy/lines');
|
||||
const data = await response.json();
|
||||
linesData = data.data;
|
||||
renderLines();
|
||||
} catch (error) {
|
||||
console.error('加载线路失败:', error);
|
||||
showResult('加载线路信息失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
const clientId = document.getElementById('client-id').textContent;
|
||||
// 渲染线路表格
|
||||
function renderLines() {
|
||||
const tbody = document.getElementById('lines-table-body');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
linesData.forEach((line, index) => {
|
||||
const row = document.createElement('tr');
|
||||
const lastRotateTime = line.last_rotate_time ?
|
||||
new Date(line.last_rotate_time).toLocaleString('zh-CN') : '从未轮换';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${index + 1}</td>
|
||||
<td>${line.port}</td>
|
||||
<td><span class="ip-address">${line.ip}</span></td>
|
||||
<td><span class="status-${line.status}">${line.status === 'active' ? '活跃' : '离线'}</span></td>
|
||||
<td><span class="rotate-count">${line.rotate_count || 0}</span></td>
|
||||
<td><span class="last-rotate-time">${lastRotateTime}</span></td>
|
||||
<td>
|
||||
<button class="btn" onclick="rotateLine(${line.id})" id="btn-${line.id}">
|
||||
🔄 换IP
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// 执行单条线路IP轮换
|
||||
async function rotateLine(lineId) {
|
||||
const button = document.getElementById(`btn-${lineId}`);
|
||||
const originalText = button.innerHTML;
|
||||
|
||||
// 显示加载状态
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('rotate-btn').disabled = true;
|
||||
document.getElementById('result').style.display = 'none';
|
||||
button.disabled = true;
|
||||
button.classList.add('rotating');
|
||||
button.innerHTML = '⏳ 轮换中...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/proxy/rotate', {
|
||||
@ -320,8 +440,8 @@
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: parseInt(clientId),
|
||||
citys: selectedProvinces.join(',')
|
||||
id: lineId,
|
||||
citys: selectedProvinces.join(',') // 使用选中的省份
|
||||
})
|
||||
});
|
||||
|
||||
@ -329,24 +449,32 @@
|
||||
|
||||
if (result.changed) {
|
||||
showResult(`
|
||||
<h3>✅ IP轮换成功!</h3>
|
||||
<h3>✅ 线路 ${lineId} IP轮换成功!</h3>
|
||||
<p><strong>新IP地址:</strong> ${result.ip}</p>
|
||||
<p><strong>边缘设备:</strong> ${result.edge}</p>
|
||||
<p><strong>网关状态:</strong> 已更新</p>
|
||||
<p><strong>地理位置:</strong> ${result.geo}</p>
|
||||
`, 'success');
|
||||
|
||||
// 更新表格中的IP地址
|
||||
const line = linesData.find(l => l.id === lineId);
|
||||
if (line) {
|
||||
line.ip = result.ip;
|
||||
renderLines(); // 重新渲染表格
|
||||
}
|
||||
} else {
|
||||
showResult(`
|
||||
<h3>⚠️ IP轮换失败</h3>
|
||||
<h3>⚠️ 线路 ${lineId} IP轮换失败</h3>
|
||||
<p><strong>原因:</strong> ${result.reason}</p>
|
||||
`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('轮换失败:', error);
|
||||
showResult('IP轮换失败: ' + error.message, 'error');
|
||||
showResult('线路 ' + lineId + ' IP轮换失败: ' + error.message, 'error');
|
||||
} finally {
|
||||
// 隐藏加载状态
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('rotate-btn').disabled = false;
|
||||
// 恢复按钮状态
|
||||
button.disabled = false;
|
||||
button.classList.remove('rotating');
|
||||
button.innerHTML = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,6 +484,13 @@
|
||||
resultDiv.innerHTML = message;
|
||||
resultDiv.className = `result ${type}`;
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
// 3秒后自动隐藏成功消息
|
||||
if (type === 'success') {
|
||||
setTimeout(() => {
|
||||
resultDiv.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
32
line_status.json
Normal file
32
line_status.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"1": {
|
||||
"id": 1,
|
||||
"current_ip": "222.169.52.224",
|
||||
"last_rotate_time": "2025-10-22T14:45:11.633459",
|
||||
"status": "active",
|
||||
"edge_device": "DCD87C27D4D6",
|
||||
"geo_location": "a4002c19888160b7f7875e7d5727ad8a0c7323af8070f39dcf60895112ac1283",
|
||||
"rotate_count": 8,
|
||||
"last_update_time": "2025-10-22T14:45:11.645492"
|
||||
},
|
||||
"2": {
|
||||
"id": 2,
|
||||
"current_ip": "39.128.153.88",
|
||||
"last_rotate_time": "2025-10-22T14:41:24.720185",
|
||||
"status": "active",
|
||||
"edge_device": "DCD87C29EB38",
|
||||
"geo_location": "6dafce691415eeb72b86b9529e6d05773cf7e94df13957d3df64b37267cc2fe8",
|
||||
"rotate_count": 5,
|
||||
"last_update_time": "2025-10-22T14:41:24.729735"
|
||||
},
|
||||
"3": {
|
||||
"id": 3,
|
||||
"current_ip": "119.55.16.172",
|
||||
"last_rotate_time": "2025-10-22T14:34:16.421955",
|
||||
"status": "active",
|
||||
"edge_device": "DCD87C44F885",
|
||||
"geo_location": "bc41d34110c2989b29f7521133d605e7a8aa6e9edb488217c5c0d087603a753c",
|
||||
"rotate_count": 2,
|
||||
"last_update_time": "2025-10-22T14:34:16.436191"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user