添加刷新按钮
This commit is contained in:
@@ -1873,6 +1873,11 @@ input:checked+.slider:before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Animations ---- */
|
/* ---- Animations ---- */
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -242,6 +242,13 @@
|
|||||||
</svg>
|
</svg>
|
||||||
网络流量趋势 (24h)
|
网络流量趋势 (24h)
|
||||||
</h2>
|
</h2>
|
||||||
|
<div class="chart-header-actions">
|
||||||
|
<button class="btn-icon" id="btnRefreshNetwork" title="刷新流量趋势">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 16px; height: 16px;">
|
||||||
|
<path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-legend">
|
<div class="chart-legend">
|
||||||
<span class="legend-item" id="legendRx" style="cursor: pointer;" title="点击切换 接收 (RX) 显示/隐藏"><span
|
<span class="legend-item" id="legendRx" style="cursor: pointer;" title="点击切换 接收 (RX) 显示/隐藏"><span
|
||||||
|
|||||||
@@ -110,6 +110,7 @@
|
|||||||
partitionHeader: document.getElementById('partitionHeader'),
|
partitionHeader: document.getElementById('partitionHeader'),
|
||||||
globeCard: document.getElementById('globeCard'),
|
globeCard: document.getElementById('globeCard'),
|
||||||
btnExpandGlobe: document.getElementById('btnExpandGlobe'),
|
btnExpandGlobe: document.getElementById('btnExpandGlobe'),
|
||||||
|
btnRefreshNetwork: document.getElementById('btnRefreshNetwork'),
|
||||||
// Footer & Filing
|
// Footer & Filing
|
||||||
icpFilingInput: document.getElementById('icpFilingInput'),
|
icpFilingInput: document.getElementById('icpFilingInput'),
|
||||||
psFilingInput: document.getElementById('psFilingInput'),
|
psFilingInput: document.getElementById('psFilingInput'),
|
||||||
@@ -353,6 +354,25 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dom.btnRefreshNetwork) {
|
||||||
|
dom.btnRefreshNetwork.addEventListener('click', async () => {
|
||||||
|
const icon = dom.btnRefreshNetwork.querySelector('svg');
|
||||||
|
if (icon) icon.style.animation = 'spin 0.8s ease-in-out';
|
||||||
|
|
||||||
|
// Force refresh all Prometheus 24h data and overview
|
||||||
|
await Promise.all([
|
||||||
|
fetchNetworkHistory(true),
|
||||||
|
fetchMetrics(true)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
setTimeout(() => {
|
||||||
|
icon.style.animation = '';
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Keyboard shortcut
|
// Keyboard shortcut
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
@@ -693,9 +713,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Fetch Metrics ----
|
// ---- Fetch Metrics ----
|
||||||
async function fetchMetrics() {
|
async function fetchMetrics(force = false) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/metrics/overview');
|
const url = `/api/metrics/overview${force ? '?force=true' : ''}`;
|
||||||
|
const response = await fetch(url);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
allServersData = data.servers || [];
|
allServersData = data.servers || [];
|
||||||
updateDashboard(data);
|
updateDashboard(data);
|
||||||
@@ -1619,9 +1640,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ---- Network History ----
|
// ---- Network History ----
|
||||||
async function fetchNetworkHistory() {
|
async function fetchNetworkHistory(force = false) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/metrics/network-history');
|
const url = `/api/metrics/network-history${force ? '?force=true' : ''}`;
|
||||||
|
const response = await fetch(url);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
networkChart.setData(data);
|
networkChart.setData(data);
|
||||||
if (dom.trafficP95 && networkChart.p95) {
|
if (dom.trafficP95 && networkChart.p95) {
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ app.post('/api/settings', requireAuth, async (req, res) => {
|
|||||||
// ==================== Metrics Aggregation ====================
|
// ==================== Metrics Aggregation ====================
|
||||||
|
|
||||||
// Reusable function to get overview metrics
|
// Reusable function to get overview metrics
|
||||||
async function getOverview() {
|
async function getOverview(force = false) {
|
||||||
const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"');
|
const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"');
|
||||||
if (sources.length === 0) {
|
if (sources.length === 0) {
|
||||||
return {
|
return {
|
||||||
@@ -680,8 +680,12 @@ async function getOverview() {
|
|||||||
|
|
||||||
const allMetrics = await Promise.all(sources.map(async (source) => {
|
const allMetrics = await Promise.all(sources.map(async (source) => {
|
||||||
const cacheKey = `source_metrics:${source.url}:${source.name}`;
|
const cacheKey = `source_metrics:${source.url}:${source.name}`;
|
||||||
|
if (force) {
|
||||||
|
await cache.del(cacheKey);
|
||||||
|
} else {
|
||||||
const cached = await cache.get(cacheKey);
|
const cached = await cache.get(cacheKey);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const metrics = await prometheusService.getOverviewMetrics(source.url, source.name);
|
const metrics = await prometheusService.getOverviewMetrics(source.url, source.name);
|
||||||
@@ -790,7 +794,8 @@ async function getOverview() {
|
|||||||
// Get all aggregated metrics from all Prometheus sources
|
// Get all aggregated metrics from all Prometheus sources
|
||||||
app.get('/api/metrics/overview', async (req, res) => {
|
app.get('/api/metrics/overview', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const overview = await getOverview();
|
const force = req.query.force === 'true';
|
||||||
|
const overview = await getOverview(force);
|
||||||
res.json(overview);
|
res.json(overview);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching overview metrics:', err);
|
console.error('Error fetching overview metrics:', err);
|
||||||
@@ -801,9 +806,15 @@ app.get('/api/metrics/overview', async (req, res) => {
|
|||||||
// Get network traffic history (past 24h) from Prometheus
|
// Get network traffic history (past 24h) from Prometheus
|
||||||
app.get('/api/metrics/network-history', async (req, res) => {
|
app.get('/api/metrics/network-history', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const force = req.query.force === 'true';
|
||||||
const cacheKey = 'network_history_all';
|
const cacheKey = 'network_history_all';
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
await cache.del(cacheKey);
|
||||||
|
} else {
|
||||||
const cached = await cache.get(cacheKey);
|
const cached = await cache.get(cacheKey);
|
||||||
if (cached) return res.json(cached);
|
if (cached) return res.json(cached);
|
||||||
|
}
|
||||||
|
|
||||||
const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"');
|
const [sources] = await db.query('SELECT * FROM prometheus_sources WHERE is_server_source = 1 AND type != "blackbox"');
|
||||||
if (sources.length === 0) {
|
if (sources.length === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user