1import os from "os";
2import { postText } from "../util/http.js";
3
4const escapeTag = (value) => String(value).replace(/[ ,=]/g, "\\$&");
5
6const buildLine = (measurement, tags, fields, timestampSeconds) => {
7 const tagPart = Object.entries(tags)
8 .filter(([, v]) => v !== undefined && v !== null && v !== "")
9 .map(([k, v]) => `${escapeTag(k)}=${escapeTag(v)}`)
10 .join(",");
11
12 const fieldPart = Object.entries(fields)
13 .filter(([, v]) => typeof v === "number" && Number.isFinite(v))
14 .map(([k, v]) => `${k}=${v}`)
15 .join(",");
16
17 const prefix = tagPart ? `${measurement},${tagPart}` : measurement;
18 return `${prefix} ${fieldPart} ${timestampSeconds}`;
19};
20
21const send = (c, line, activity) => {
22 const baseUrl = c.url.replace(/\/+$/, "");
23 const url = `${baseUrl}/api/v2/write?org=${encodeURIComponent(c.org)}` +
24 `&bucket=${encodeURIComponent(c.bucket)}&precision=s`;
25 return postText(url, line, {
26 headers: {
27 "Authorization": `Token ${c.token}`,
28 "Content-Type": "text/plain; charset=utf-8"
29 },
30 activity
31 });
32};
33
34export default (registerEvent) => {
35 registerEvent("testFinished", async ({data: c}, data, activity) => {
36 const tags = {
37 host: c.host || os.hostname(),
38 ...(c.tags ? Object.fromEntries(c.tags.split(",")
39 .map(t => t.split("=").map(s => s.trim()))
40 .filter(([k, v]) => k && v)) : {})
41 };
42
43 const fields = {
44 download: data.download,
45 upload: data.upload,
46 ping: data.ping,
47 jitter: data.jitter ?? 0
48 };
49
50 const timestamp = Math.floor(Date.now() / 1000);
51 const line = buildLine(c.measurement || "speedtests", tags, fields, timestamp);
52
53 await send(c, line, activity);
54 });
55
56 return {
57 icon: "fa-solid fa-database",
58 fields: [
59 {name: "url", type: "text", required: true, regex: /^https?:\/\/.+/},
60 {name: "org", type: "text", required: true},
61 {name: "bucket", type: "text", required: true},
62 {name: "token", type: "text", required: true},
63 {name: "measurement", type: "text", required: false},
64 {name: "host", type: "text", required: false},
65 {name: "tags", type: "text", required: false}
66 ]
67 };
68};
69