mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-18 10:02:16 +00:00
Update notification service
This commit is contained in:
@@ -125,13 +125,20 @@ const generateLatencyReport = (report: ReportData) => {
|
|||||||
const logoUrl = `${window.location.origin}/images/proxmenux-logo.png`
|
const logoUrl = `${window.location.origin}/images/proxmenux-logo.png`
|
||||||
|
|
||||||
// Calculate stats for realtime results
|
// Calculate stats for realtime results
|
||||||
const realtimeStats = report.realtimeResults.length > 0 ? {
|
const realtimeStats = report.realtimeResults.length > 0 ? (() => {
|
||||||
min: Math.min(...report.realtimeResults.filter(r => r.latency_min !== null).map(r => r.latency_min!)),
|
const validResults = report.realtimeResults.filter(r => r.latency_avg !== null)
|
||||||
max: Math.max(...report.realtimeResults.filter(r => r.latency_max !== null).map(r => r.latency_max!)),
|
const minValues = report.realtimeResults.filter(r => r.latency_min !== null).map(r => r.latency_min!)
|
||||||
avg: report.realtimeResults.reduce((acc, r) => acc + (r.latency_avg || 0), 0) / report.realtimeResults.length,
|
const maxValues = report.realtimeResults.filter(r => r.latency_max !== null).map(r => r.latency_max!)
|
||||||
current: report.realtimeResults[report.realtimeResults.length - 1]?.latency_avg ?? null,
|
const avgValues = validResults.map(r => r.latency_avg!)
|
||||||
avgPacketLoss: report.realtimeResults.reduce((acc, r) => acc + (r.packet_loss || 0), 0) / report.realtimeResults.length,
|
|
||||||
} : null
|
return {
|
||||||
|
min: minValues.length > 0 ? Math.min(...minValues) : (avgValues.length > 0 ? Math.min(...avgValues) : 0),
|
||||||
|
max: maxValues.length > 0 ? Math.max(...maxValues) : (avgValues.length > 0 ? Math.max(...avgValues) : 0),
|
||||||
|
avg: validResults.length > 0 ? validResults.reduce((acc, r) => acc + (r.latency_avg || 0), 0) / validResults.length : 0,
|
||||||
|
current: report.realtimeResults[report.realtimeResults.length - 1]?.latency_avg ?? null,
|
||||||
|
avgPacketLoss: report.realtimeResults.reduce((acc, r) => acc + (r.packet_loss || 0), 0) / report.realtimeResults.length,
|
||||||
|
}
|
||||||
|
})() : null
|
||||||
|
|
||||||
const statusText = report.isRealtime
|
const statusText = report.isRealtime
|
||||||
? getStatusText(realtimeStats?.current ?? null)
|
? getStatusText(realtimeStats?.current ?? null)
|
||||||
@@ -170,17 +177,11 @@ const generateLatencyReport = (report: ReportData) => {
|
|||||||
endTime: new Date(report.data[report.data.length - 1].timestamp * 1000).toLocaleString(),
|
endTime: new Date(report.data[report.data.length - 1].timestamp * 1000).toLocaleString(),
|
||||||
} : null
|
} : null
|
||||||
|
|
||||||
// Generate chart SVG - expand realtime to all 3 values (min, avg, max) per sample
|
// Generate chart SVG - use average values for the line chart
|
||||||
const chartData = report.isRealtime
|
const chartData = report.isRealtime
|
||||||
? report.realtimeResults.flatMap(r => {
|
? report.realtimeResults
|
||||||
const points: number[] = []
|
.filter(r => r.latency_avg !== null)
|
||||||
if (r.latency_min !== null) points.push(r.latency_min)
|
.map(r => r.latency_avg!)
|
||||||
if (r.latency_avg !== null && r.latency_avg !== r.latency_min && r.latency_avg !== r.latency_max) {
|
|
||||||
points.push(r.latency_avg)
|
|
||||||
}
|
|
||||||
if (r.latency_max !== null) points.push(r.latency_max)
|
|
||||||
return points.length > 0 ? points : [r.latency_avg ?? 0]
|
|
||||||
})
|
|
||||||
: report.data.map(d => d.value || 0)
|
: report.data.map(d => d.value || 0)
|
||||||
|
|
||||||
let chartSvg = '<p style="text-align:center;color:#64748b;padding:20px;">Not enough data points for chart</p>'
|
let chartSvg = '<p style="text-align:center;color:#64748b;padding:20px;">Not enough data points for chart</p>'
|
||||||
@@ -779,30 +780,32 @@ export function LatencyDetailModal({ open, onOpenChange, currentLatency }: Laten
|
|||||||
time: new Date(point.timestamp * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
time: new Date(point.timestamp * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Expand each sample to 3 data points (min, avg, max) for accurate representation
|
// Use average value for the chart line (min/max are shown in stats)
|
||||||
const realtimeChartData = realtimeResults.flatMap((r, i) => {
|
const realtimeChartData = realtimeResults
|
||||||
const time = new Date(r.timestamp || Date.now()).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
.filter(r => r.latency_avg !== null)
|
||||||
const points = []
|
.map(r => ({
|
||||||
if (r.latency_min !== null) points.push({ time, value: r.latency_min, packet_loss: r.packet_loss })
|
time: new Date(r.timestamp || Date.now()).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
||||||
if (r.latency_avg !== null && r.latency_avg !== r.latency_min && r.latency_avg !== r.latency_max) {
|
value: r.latency_avg,
|
||||||
points.push({ time, value: r.latency_avg, packet_loss: r.packet_loss })
|
min: r.latency_min,
|
||||||
}
|
max: r.latency_max,
|
||||||
if (r.latency_max !== null) points.push({ time, value: r.latency_max, packet_loss: r.packet_loss })
|
packet_loss: r.packet_loss
|
||||||
// If no valid points, add avg as fallback
|
}))
|
||||||
if (points.length === 0 && r.latency_avg !== null) {
|
|
||||||
points.push({ time, value: r.latency_avg, packet_loss: r.packet_loss })
|
|
||||||
}
|
|
||||||
return points
|
|
||||||
})
|
|
||||||
|
|
||||||
// Calculate realtime stats
|
// Calculate realtime stats
|
||||||
const realtimeStats = realtimeResults.length > 0 ? {
|
const realtimeStats = realtimeResults.length > 0 ? (() => {
|
||||||
current: realtimeResults[realtimeResults.length - 1]?.latency_avg ?? 0,
|
const validResults = realtimeResults.filter(r => r.latency_avg !== null)
|
||||||
min: Math.min(...realtimeResults.filter(r => r.latency_min !== null).map(r => r.latency_min!)) || 0,
|
const minValues = realtimeResults.filter(r => r.latency_min !== null).map(r => r.latency_min!)
|
||||||
max: Math.max(...realtimeResults.filter(r => r.latency_max !== null).map(r => r.latency_max!)) || 0,
|
const maxValues = realtimeResults.filter(r => r.latency_max !== null).map(r => r.latency_max!)
|
||||||
avg: realtimeResults.reduce((acc, r) => acc + (r.latency_avg || 0), 0) / realtimeResults.length,
|
const avgValues = validResults.map(r => r.latency_avg!)
|
||||||
packetLoss: realtimeResults[realtimeResults.length - 1]?.packet_loss ?? 0,
|
|
||||||
} : null
|
return {
|
||||||
|
current: realtimeResults[realtimeResults.length - 1]?.latency_avg ?? 0,
|
||||||
|
min: minValues.length > 0 ? Math.min(...minValues) : (avgValues.length > 0 ? Math.min(...avgValues) : 0),
|
||||||
|
max: maxValues.length > 0 ? Math.max(...maxValues) : (avgValues.length > 0 ? Math.max(...avgValues) : 0),
|
||||||
|
avg: validResults.length > 0 ? validResults.reduce((acc, r) => acc + (r.latency_avg || 0), 0) / validResults.length : 0,
|
||||||
|
packetLoss: realtimeResults[realtimeResults.length - 1]?.packet_loss ?? 0,
|
||||||
|
}
|
||||||
|
})() : null
|
||||||
|
|
||||||
const displayStats = isRealtime ? {
|
const displayStats = isRealtime ? {
|
||||||
current: realtimeStats?.current ?? 0,
|
current: realtimeStats?.current ?? 0,
|
||||||
|
|||||||
@@ -631,34 +631,36 @@ def init_latency_db():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _measure_latency(target_ip: str) -> dict:
|
def _measure_latency(target_ip: str) -> dict:
|
||||||
"""Ping a target and return latency stats. Uses 3 pings and returns the average to avoid false positives."""
|
"""Ping a target and return latency stats. Uses 3 pings and returns avg, min, max for full visibility."""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['ping', '-c', '3', '-W', '2', target_ip],
|
['ping', '-c', '3', '-W', '2', target_ip],
|
||||||
capture_output=True, text=True, timeout=10
|
capture_output=True, text=True, timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
latencies = []
|
latencies = []
|
||||||
for line in result.stdout.split('\n'):
|
for line in result.stdout.split('\n'):
|
||||||
if 'time=' in line:
|
if 'time=' in line:
|
||||||
try:
|
try:
|
||||||
latency_str = line.split('time=')[1].split()[0]
|
latency_str = line.split('time=')[1].split()[0]
|
||||||
latencies.append(float(latency_str))
|
latencies.append(float(latency_str))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if latencies:
|
if latencies:
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'avg': round(sum(latencies) / len(latencies), 1),
|
'avg': round(sum(latencies) / len(latencies), 1),
|
||||||
'packet_loss': round((3 - len(latencies)) / 3 * 100, 1)
|
'min': round(min(latencies), 1),
|
||||||
}
|
'max': round(max(latencies), 1),
|
||||||
|
'packet_loss': round((3 - len(latencies)) / 3 * 100, 1)
|
||||||
# Ping failed - 100% packet loss
|
}
|
||||||
return {'success': False, 'avg': None, 'packet_loss': 100.0}
|
|
||||||
except Exception:
|
# Ping failed - 100% packet loss
|
||||||
return {'success': False, 'avg': None, 'packet_loss': 100.0}
|
return {'success': False, 'avg': None, 'min': None, 'max': None, 'packet_loss': 100.0}
|
||||||
|
except Exception:
|
||||||
|
return {'success': False, 'avg': None, 'min': None, 'max': None, 'packet_loss': 100.0}
|
||||||
|
|
||||||
def _record_latency():
|
def _record_latency():
|
||||||
"""Record latency to the default gateway. Only stores the average of 3 pings."""
|
"""Record latency to the default gateway. Only stores the average of 3 pings."""
|
||||||
@@ -807,19 +809,21 @@ def get_current_latency(target='gateway'):
|
|||||||
# Fallback: do fresh measurement if no stored data
|
# Fallback: do fresh measurement if no stored data
|
||||||
stats = _measure_latency(target_ip)
|
stats = _measure_latency(target_ip)
|
||||||
else:
|
else:
|
||||||
# Cloudflare/Google: fresh ping (not continuously monitored)
|
# Cloudflare/Google: fresh ping (not continuously monitored)
|
||||||
target_ip = LATENCY_TARGETS.get(target, target)
|
target_ip = LATENCY_TARGETS.get(target, target)
|
||||||
stats = _measure_latency(target_ip)
|
stats = _measure_latency(target_ip)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'target': target,
|
'target': target,
|
||||||
'target_ip': target_ip,
|
'target_ip': target_ip,
|
||||||
'latency_avg': stats['avg'],
|
'latency_avg': stats['avg'],
|
||||||
'packet_loss': stats['packet_loss'],
|
'latency_min': stats.get('min'),
|
||||||
'status': 'ok' if stats['success'] and stats['avg'] and stats['avg'] < 100 else 'warning' if stats['success'] else 'error'
|
'latency_max': stats.get('max'),
|
||||||
}
|
'packet_loss': stats['packet_loss'],
|
||||||
except Exception:
|
'status': 'ok' if stats['success'] and stats['avg'] and stats['avg'] < 100 else 'warning' if stats['success'] else 'error'
|
||||||
return {'target': target, 'latency_avg': None, 'status': 'error'}
|
}
|
||||||
|
except Exception:
|
||||||
|
return {'target': target, 'latency_avg': None, 'latency_min': None, 'latency_max': None, 'status': 'error'}
|
||||||
|
|
||||||
|
|
||||||
def _health_collector_loop():
|
def _health_collector_loop():
|
||||||
|
|||||||
Reference in New Issue
Block a user