feat:自动更换ip+流量监控

This commit is contained in:
2026-01-07 17:19:53 +08:00
commit 035da64084
27 changed files with 6182 additions and 0 deletions

311
templates/traffic.html Normal file
View File

@@ -0,0 +1,311 @@
{% extends "base.html" %}
{% block title %}流量监控 - ProxyAuto Pro{% endblock %}
{% block content %}
<div class="page-header">
<h1>流量监控</h1>
</div>
{% if not machines %}
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
<rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
<line x1="6" y1="6" x2="6.01" y2="6"/>
<line x1="6" y1="18" x2="6.01" y2="18"/>
</svg>
<h3>暂无节点</h3>
<p>请先前往<a href="{{ url_for('machines') }}">节点管理</a>添加机器</p>
</div>
{% else %}
<section class="section">
<div class="traffic-query-card form-card">
<h3 class="form-section-title">查询条件</h3>
<div class="form-grid">
<div class="form-group">
<label for="machine_id">选择节点</label>
<select id="machine_id" name="machine_id">
{% for machine in machines %}
<option value="{{ machine.id }}" data-service="{{ machine.aws_service }}">
{{ machine.name }} ({{ machine.aws_service|upper }})
</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="query_type">查询类型</label>
<select id="query_type" name="query_type" onchange="updateDateInputs()">
<option value="month">按月查询</option>
<option value="day">按天查询</option>
<option value="range">自定义时间段</option>
</select>
</div>
</div>
<div id="month-inputs" class="form-grid">
<div class="form-group">
<label for="query_year">年份</label>
<select id="query_year">
<option value="2026">2026</option>
<option value="2025">2025</option>
</select>
</div>
<div class="form-group">
<label for="query_month">月份</label>
<select id="query_month">
<option value="1">1月</option>
<option value="2">2月</option>
<option value="3">3月</option>
<option value="4">4月</option>
<option value="5">5月</option>
<option value="6">6月</option>
<option value="7">7月</option>
<option value="8">8月</option>
<option value="9">9月</option>
<option value="10">10月</option>
<option value="11">11月</option>
<option value="12">12月</option>
</select>
</div>
</div>
<div id="day-inputs" class="form-grid" style="display: none;">
<div class="form-group">
<label for="query_date">日期</label>
<input type="text" id="query_date" class="datepicker" placeholder="点击选择日期" readonly>
</div>
</div>
<div id="range-inputs" class="form-grid" style="display: none;">
<div class="form-group">
<label for="start_date">开始日期</label>
<input type="text" id="start_date" class="datepicker" placeholder="点击选择日期" readonly>
</div>
<div class="form-group">
<label for="end_date">结束日期</label>
<input type="text" id="end_date" class="datepicker" placeholder="点击选择日期" readonly>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-primary" onclick="queryTraffic()">
<svg class="btn-icon-normal" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<svg class="btn-icon-loading spinner" viewBox="0 0 24 24" style="display: none;">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" fill="none" stroke-dasharray="32" stroke-linecap="round"/>
</svg>
<span class="btn-text">查询流量</span>
</button>
</div>
</div>
</section>
<div id="traffic-result" style="display: none;">
<section class="section">
<h2 class="section-title">流量统计</h2>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">下载流量</div>
<div class="metric-value" id="network-in">--</div>
</div>
<div class="metric-card">
<div class="metric-label">上传流量</div>
<div class="metric-value" id="network-out">--</div>
</div>
<div class="metric-card">
<div class="metric-label">总流量</div>
<div class="metric-value" id="network-total">--</div>
</div>
</div>
</section>
<section class="section">
<h2 class="section-title">流量趋势</h2>
<div class="chart-container">
<canvas id="traffic-chart"></canvas>
</div>
</section>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/material_blue.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/zh.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
let trafficChart = null;
let queryDatePicker, startDatePicker, endDatePicker;
// 初始化日期选择器
document.addEventListener('DOMContentLoaded', function() {
const now = new Date();
document.getElementById('query_year').value = now.getFullYear();
document.getElementById('query_month').value = now.getMonth() + 1;
// Flatpickr 通用配置
const fpConfig = {
locale: 'zh',
dateFormat: 'Y-m-d',
disableMobile: true,
allowInput: false,
defaultDate: now,
};
// 初始化日期选择器
queryDatePicker = flatpickr('#query_date', fpConfig);
startDatePicker = flatpickr('#start_date', fpConfig);
endDatePicker = flatpickr('#end_date', fpConfig);
});
function updateDateInputs() {
const type = document.getElementById('query_type').value;
document.getElementById('month-inputs').style.display = type === 'month' ? 'grid' : 'none';
document.getElementById('day-inputs').style.display = type === 'day' ? 'grid' : 'none';
document.getElementById('range-inputs').style.display = type === 'range' ? 'grid' : 'none';
}
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + units[i];
}
async function queryTraffic() {
const btn = document.querySelector('.form-actions button');
const normalIcon = btn.querySelector('.btn-icon-normal');
const loadingIcon = btn.querySelector('.btn-icon-loading');
const btnText = btn.querySelector('.btn-text');
btn.disabled = true;
normalIcon.style.display = 'none';
loadingIcon.style.display = 'block';
btnText.textContent = '查询中...';
try {
const machineId = document.getElementById('machine_id').value;
const queryType = document.getElementById('query_type').value;
let startDate, endDate;
if (queryType === 'month') {
const year = document.getElementById('query_year').value;
const month = document.getElementById('query_month').value;
startDate = `${year}-${month.padStart(2, '0')}-01`;
// 计算月末
const lastDay = new Date(year, month, 0).getDate();
endDate = `${year}-${month.padStart(2, '0')}-${lastDay}`;
} else if (queryType === 'day') {
startDate = document.getElementById('query_date').value;
endDate = startDate;
} else {
startDate = document.getElementById('start_date').value;
endDate = document.getElementById('end_date').value;
}
const response = await fetch(`/api/traffic/${machineId}?start=${startDate}&end=${endDate}`);
const result = await response.json();
if (result.ok) {
displayTrafficResult(result);
} else {
showToast('error', result.message || '查询失败');
}
} catch (error) {
showToast('error', `查询失败: ${error.message}`);
} finally {
btn.disabled = false;
normalIcon.style.display = 'block';
loadingIcon.style.display = 'none';
btnText.textContent = '查询流量';
}
}
function displayTrafficResult(data) {
document.getElementById('traffic-result').style.display = 'block';
document.getElementById('network-in').textContent = formatBytes(data.network_in);
document.getElementById('network-out').textContent = formatBytes(data.network_out);
document.getElementById('network-total').textContent = formatBytes(data.total);
// 绘制图表
renderChart(data.data_points);
}
function renderChart(dataPoints) {
const ctx = document.getElementById('traffic-chart').getContext('2d');
if (trafficChart) {
trafficChart.destroy();
}
const labels = dataPoints.map(dp => {
const date = new Date(dp.timestamp);
return `${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:00`;
});
const inData = dataPoints.map(dp => dp.network_in / (1024 * 1024)); // MB
const outData = dataPoints.map(dp => dp.network_out / (1024 * 1024)); // MB
trafficChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '下载 (MB)',
data: inData,
borderColor: '#10B981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
fill: true,
tension: 0.3,
},
{
label: '上传 (MB)',
data: outData,
borderColor: '#1F6BFF',
backgroundColor: 'rgba(31, 107, 255, 0.1)',
fill: true,
tension: 0.3,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
}
},
scales: {
x: {
display: true,
title: {
display: true,
text: '时间'
}
},
y: {
display: true,
title: {
display: true,
text: '流量 (MB)'
},
beginAtZero: true
}
}
}
});
}
</script>
{% endblock %}