mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-30 05:17:49 +02:00
82 lines
3.1 KiB
TypeScript
82 lines
3.1 KiB
TypeScript
import type {
|
|
MonitorCertInfo,
|
|
MonitorStats,
|
|
NetworkMonitorRecord,
|
|
NetworkMonitorStatsRecord,
|
|
RawMonitorStatsRecord,
|
|
} from "@/types"
|
|
import { toFixedFloat } from "./utils"
|
|
|
|
/** Derive chart metrics from the counts and response sum stored at every retention tier. */
|
|
export function getMonitorStats(record: RawMonitorStatsRecord): MonitorStats {
|
|
const success = record.success_count > 0
|
|
return {
|
|
res_avg: success ? toFixedFloat(record.res_sum / record.success_count, 2) : null,
|
|
res_min: success ? record.res_min : null,
|
|
res_max: success ? record.res_max : null,
|
|
loss:
|
|
record.total_count > 0
|
|
? toFixedFloat(((record.total_count - record.success_count) / record.total_count) * 100, 2)
|
|
: 0,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Realtime stats come from the agent without counts and report 0 response times when every
|
|
* probe failed; clear them to match stored stats.
|
|
*/
|
|
export function clearFailedResponse(stats: MonitorStats): MonitorStats {
|
|
if (stats.loss < 100) return stats
|
|
return { ...stats, res_avg: null, res_min: null, res_max: null }
|
|
}
|
|
|
|
/**
|
|
* Gap marker in the same form appendData uses. Without a timestamp it can't become the active
|
|
* tooltip point, which would otherwise have no values and make the tooltip jump to the corner.
|
|
*/
|
|
export const monitorGapRecord = { created: null, stats: null } as unknown as NetworkMonitorStatsRecord
|
|
|
|
/**
|
|
* Return the records that have stats for one monitor, with a gap marker inserted wherever
|
|
* consecutive records are further apart than expected (e.g. while the agent was disconnected),
|
|
* so charts break the line there instead of drawing across the missing time.
|
|
*/
|
|
export function withMonitorGaps(
|
|
records: NetworkMonitorStatsRecord[],
|
|
monitor: Pick<NetworkMonitorRecord, "id" | "interval">,
|
|
expectedInterval: number
|
|
): NetworkMonitorStatsRecord[] {
|
|
// long-interval monitors only get a record when a new probe completes
|
|
const maxGap = Math.max(expectedInterval, monitor.interval * 1000) * 1.5
|
|
const result: NetworkMonitorStatsRecord[] = []
|
|
let prevTime = 0
|
|
for (const record of records) {
|
|
// skip appendData's gap markers (created: null) and records without this monitor
|
|
if (record.created == null || !record.stats?.[monitor.id]) continue
|
|
if (prevTime && record.created - prevTime > maxGap) {
|
|
result.push(monitorGapRecord)
|
|
}
|
|
prevTime = record.created
|
|
result.push(record)
|
|
}
|
|
return result
|
|
}
|
|
|
|
export function getMonitorTarget(monitor: Pick<NetworkMonitorRecord, "target" | "protocol" | "port">) {
|
|
if (monitor.protocol !== "tcp") return monitor.target
|
|
const host = monitor.target.includes(":") && !monitor.target.startsWith("[") ? `[${monitor.target}]` : monitor.target
|
|
return `${host}:${monitor.port}`
|
|
}
|
|
|
|
/** Whole days until the certificate expires; negative once expired. */
|
|
export function getCertDaysLeft(cert: Pick<MonitorCertInfo, "expires">, now = Date.now()) {
|
|
return Math.floor((cert.expires - now) / 86_400_000)
|
|
}
|
|
|
|
/** Expiry severity used for certificate colors. */
|
|
export function getCertExpiryLevel(daysLeft: number): "ok" | "warning" | "critical" {
|
|
if (daysLeft < 7) return "critical"
|
|
if (daysLeft < 14) return "warning"
|
|
return "ok"
|
|
}
|