Files
MikrotikManager/backend/src/services/ros-metric-parse.ts
T
DenozordecandCursor 5f31bb47fb chore: synchronize pending app/backend updates and repository hygiene
Includes current frontend and backend work in progress and removes generated artifacts from tracking to keep the repository clean for дальнейшая разработка.

Co-authored-by: Cursor <[email protected]>
2026-05-07 12:29:04 +07:00

107 lines
4.0 KiB
TypeScript

/**
* Разбор полей /system/resource из RouterOS REST: не всегда «сырые» байты в виде десятичной строки.
* На железе часто встречаются значения вида "224.0MiB", "94.2MiB"; parseInt от этого даёт мусор или 0.
*/
export function parseRosDataSizeBytes(raw: unknown): number {
if (raw == null) return 0
if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(0, Math.floor(raw))
let s = String(raw).trim().replace(/\s+/g, "")
if (!s) return 0
if (/^\d+$/.test(s)) {
const n = Number.parseInt(s, 10)
return Number.isFinite(n) ? Math.max(0, n) : 0
}
const m = s.match(/^(\d+(?:[.,]\d+)?)(KiB|MiB|GiB|TiB|Ki|Mi|Gi|Ti|KB|MB|GB|TB)$/i)
if (m) {
const v = Number.parseFloat(m[1].replace(",", "."))
if (!Number.isFinite(v) || v < 0) return 0
const u = m[2].toLowerCase()
const mult =
u === "ki" || u === "kib" || u === "kb" ? 1024
: u === "mi" || u === "mib" || u === "mb" ? 1024 ** 2
: u === "gi" || u === "gib" || u === "gb" ? 1024 ** 3
: u === "ti" || u === "tib" || u === "tb" ? 1024 ** 4
: 1
return Math.max(0, Math.floor(v * mult))
}
const fallback = Number.parseInt(s, 10)
return Number.isFinite(fallback) ? Math.max(0, fallback) : 0
}
/** cpu-load: "7%", "7", 7 */
export function parseRosCpuLoadPercent(raw: unknown): number {
if (raw == null) return 0
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.min(100, Math.max(0, Math.round(raw)))
}
const s = String(raw).trim().replace("%", "").replace(",", ".").trim()
if (!s) return 0
const n = Number.parseFloat(s)
if (!Number.isFinite(n)) return 0
return Math.min(100, Math.max(0, Math.round(n)))
}
/**
* `uptime` из /system/resource (REST): обычно "2d20h12m20s", "39m", "29s".
* Встречаются варианты: только секунды строкой, `d.hh:mm:ss`, `hh:mm:ss` без суффиксов.
* @see https://help.mikrotik.com/docs/display/ROS/Resource
*/
export function parseRosUptimeToSeconds(raw: unknown): number {
if (raw == null) return 0
if (typeof raw === "number" && Number.isFinite(raw)) {
return Math.max(0, Math.floor(raw))
}
const s0 = String(raw).trim().replace(/\u00a0/g, " ").trim()
if (!s0) return 0
// Только десятичные цифры — секунды (некоторые клиенты/прокси)
if (/^\d+$/.test(s0)) {
const n = Number.parseInt(s0, 10)
return Number.isFinite(n) ? Math.max(0, n) : 0
}
const s = s0.toLowerCase().replace(/\s+/g, "")
// 1d18:27:15
const dClock = s.match(/^(\d+)d(\d{1,3}):(\d{2}):(\d{2})$/i)
if (dClock) {
const d = Number.parseInt(dClock[1], 10)
const hh = Number.parseInt(dClock[2], 10)
const mm = Number.parseInt(dClock[3], 10)
const ss = Number.parseInt(dClock[4], 10)
if ([d, hh, mm, ss].every((x) => Number.isFinite(x) && x >= 0)) {
return d * 86400 + hh * 3600 + mm * 60 + ss
}
}
// 18:27:15 — только если нет суффиксов w/d/h/m/s (иначе это часть "2d20h12m20s")
if (!/[wdhms]/i.test(s0) && /^\d{1,7}:\d{2}:\d{2}$/.test(s)) {
const p = s.split(":")
if (p.length === 3) {
const hh = Number.parseInt(p[0], 10)
const mm = Number.parseInt(p[1], 10)
const sec = Number.parseInt(p[2], 10)
if ([hh, mm, sec].every((x) => Number.isFinite(x) && x >= 0)) {
return hh * 3600 + mm * 60 + sec
}
}
}
let total = 0
for (const m of s.matchAll(/(\d+)(w|d|h|m(?!s)|s)/gi)) {
const n = Number.parseInt(m[1], 10)
if (!Number.isFinite(n) || n < 0) continue
const u = m[2].toLowerCase()
switch (u) {
case "w": total += n * 604800; break
case "d": total += n * 86400; break
case "h": total += n * 3600; break
case "m": total += n * 60; break
case "s": total += n; break
default: break
}
}
return Math.max(0, total)
}