feat(speedtest): full speedtest-tracker-style automation (2.9.0)
Switch worker to the Ookla CLI (jitter, packet loss, server, ISP, shareable result URL, bytes). Migration 028 enriches speedtest_results + adds a generic app_settings store. New /speedtest page: KPIs, throughput + latency charts, window stats, configurable schedule (reschedulable cron) & low-speed alert threshold, history table. SV card gains ping/jitter + a link through to the page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,51 @@
|
||||
import { pool } from '../pool.js';
|
||||
export async function record({ down_mbps, up_mbps, ping_ms = null }) {
|
||||
|
||||
export async function record(r = {}) {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO speedtest_results (down_mbps, up_mbps, ping_ms) VALUES ($1,$2,$3) RETURNING *`,
|
||||
[down_mbps, up_mbps, ping_ms]);
|
||||
`INSERT INTO speedtest_results
|
||||
(down_mbps, up_mbps, ping_ms, jitter_ms, packet_loss, server_name, server_id,
|
||||
isp, result_url, down_bytes, up_bytes, ok, error)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) RETURNING *`,
|
||||
[r.down_mbps ?? null, r.up_mbps ?? null, r.ping_ms ?? null, r.jitter_ms ?? null,
|
||||
r.packet_loss ?? null, r.server_name ?? null, r.server_id ?? null, r.isp ?? null,
|
||||
r.result_url ?? null, r.down_bytes ?? null, r.up_bytes ?? null, r.ok ?? true, r.error ?? null]);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function history(limit = 30) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM speedtest_results ORDER BY ran_at DESC LIMIT $1`, [limit]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Rows within the last N hours (ascending for charting), capped.
|
||||
export async function range(hours = 168, limit = 1000) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM (
|
||||
SELECT * FROM speedtest_results
|
||||
WHERE ran_at >= now() - ($1 || ' hours')::interval
|
||||
ORDER BY ran_at DESC LIMIT $2
|
||||
) t ORDER BY ran_at ASC`, [hours, limit]);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function latest() {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM speedtest_results WHERE ok ORDER BY ran_at DESC LIMIT 1`);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function stats(hours = 24) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT count(*) FILTER (WHERE ok) AS n,
|
||||
count(*) FILTER (WHERE NOT ok) AS failures,
|
||||
avg(down_mbps) FILTER (WHERE ok) AS avg_down,
|
||||
min(down_mbps) FILTER (WHERE ok) AS min_down,
|
||||
max(down_mbps) FILTER (WHERE ok) AS max_down,
|
||||
avg(up_mbps) FILTER (WHERE ok) AS avg_up,
|
||||
avg(ping_ms) FILTER (WHERE ok) AS avg_ping,
|
||||
max(ping_ms) FILTER (WHERE ok) AS max_ping
|
||||
FROM speedtest_results
|
||||
WHERE ran_at >= now() - ($1 || ' hours')::interval`, [hours]);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user