feat:自动更换ip+流量监控
This commit is contained in:
311
templates/traffic.html
Normal file
311
templates/traffic.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user