refactor: pure sense compute — return data instead of db.insert
All 5 senses updated to new API: - compute(signal: AbortSignal) => Promise<T | null> - Export table for runtime-side insert - Remove drizzle-orm/libsql imports Refs uncaged/nerve#264 — 小橘 🍊(NEKO Team)
This commit is contained in:
parent
60979aaa6a
commit
252162ea8e
@ -41,7 +41,7 @@ function countPorcelainLines(output) {
|
|||||||
if (!output) return 0;
|
if (!output) return 0;
|
||||||
return output.split("\n").filter((line) => line.length > 0).length;
|
return output.split("\n").filter((line) => line.length > 0).length;
|
||||||
}
|
}
|
||||||
async function compute(db, _peers) {
|
async function compute(_signal) {
|
||||||
const root = workspaceRoot();
|
const root = workspaceRoot();
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
let branch = "";
|
let branch = "";
|
||||||
@ -55,7 +55,7 @@ async function compute(db, _peers) {
|
|||||||
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
||||||
if (inside !== "true") {
|
if (inside !== "true") {
|
||||||
gitError = "not a git work tree";
|
gitError = "not a git work tree";
|
||||||
await db.insert(snapshots).values({
|
return {
|
||||||
ts,
|
ts,
|
||||||
branch,
|
branch,
|
||||||
headShort,
|
headShort,
|
||||||
@ -64,16 +64,6 @@ async function compute(db, _peers) {
|
|||||||
aheadCount,
|
aheadCount,
|
||||||
behindCount,
|
behindCount,
|
||||||
gitError
|
gitError
|
||||||
});
|
|
||||||
return {
|
|
||||||
workspaceRoot: root,
|
|
||||||
branch,
|
|
||||||
headShort,
|
|
||||||
porcelainLines,
|
|
||||||
hasUpstream: false,
|
|
||||||
aheadCount,
|
|
||||||
behindCount,
|
|
||||||
gitError
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
||||||
@ -96,7 +86,7 @@ async function compute(db, _peers) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
gitError = gitErrorMessage(e);
|
gitError = gitErrorMessage(e);
|
||||||
}
|
}
|
||||||
await db.insert(snapshots).values({
|
return {
|
||||||
ts,
|
ts,
|
||||||
branch,
|
branch,
|
||||||
headShort,
|
headShort,
|
||||||
@ -105,18 +95,9 @@ async function compute(db, _peers) {
|
|||||||
aheadCount,
|
aheadCount,
|
||||||
behindCount,
|
behindCount,
|
||||||
gitError
|
gitError
|
||||||
});
|
|
||||||
return {
|
|
||||||
workspaceRoot: root,
|
|
||||||
branch,
|
|
||||||
headShort,
|
|
||||||
porcelainLines,
|
|
||||||
hasUpstream: hasUpstream === 1,
|
|
||||||
aheadCount,
|
|
||||||
behindCount,
|
|
||||||
gitError: gitError || void 0
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
compute
|
compute,
|
||||||
|
snapshots as table
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
export { snapshots as table } from "./schema.ts";
|
||||||
import { snapshots } from "./schema.ts";
|
|
||||||
|
|
||||||
const GIT_TIMEOUT_MS = 15_000;
|
const GIT_TIMEOUT_MS = 15_000;
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ function countPorcelainLines(output: string): number {
|
|||||||
return output.split("\n").filter((line) => line.length > 0).length;
|
return output.split("\n").filter((line) => line.length > 0).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
export async function compute(_signal: AbortSignal) {
|
||||||
const root = workspaceRoot();
|
const root = workspaceRoot();
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
|
|
||||||
@ -48,7 +47,7 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
||||||
if (inside !== "true") {
|
if (inside !== "true") {
|
||||||
gitError = "not a git work tree";
|
gitError = "not a git work tree";
|
||||||
await db.insert(snapshots).values({
|
return {
|
||||||
ts,
|
ts,
|
||||||
branch,
|
branch,
|
||||||
headShort,
|
headShort,
|
||||||
@ -57,16 +56,6 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
aheadCount,
|
aheadCount,
|
||||||
behindCount,
|
behindCount,
|
||||||
gitError,
|
gitError,
|
||||||
});
|
|
||||||
return {
|
|
||||||
workspaceRoot: root,
|
|
||||||
branch,
|
|
||||||
headShort,
|
|
||||||
porcelainLines,
|
|
||||||
hasUpstream: false,
|
|
||||||
aheadCount,
|
|
||||||
behindCount,
|
|
||||||
gitError,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +81,7 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
gitError = gitErrorMessage(e);
|
gitError = gitErrorMessage(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.insert(snapshots).values({
|
return {
|
||||||
ts,
|
ts,
|
||||||
branch,
|
branch,
|
||||||
headShort,
|
headShort,
|
||||||
@ -101,16 +90,5 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
aheadCount,
|
aheadCount,
|
||||||
behindCount,
|
behindCount,
|
||||||
gitError,
|
gitError,
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
workspaceRoot: root,
|
|
||||||
branch,
|
|
||||||
headShort,
|
|
||||||
porcelainLines,
|
|
||||||
hasUpstream: hasUpstream === 1,
|
|
||||||
aheadCount,
|
|
||||||
behindCount,
|
|
||||||
gitError: gitError || undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -274,7 +274,7 @@ async function countDirectChildren(mainPid) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function compute(db, _peers) {
|
async function compute(_signal) {
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
let mainPid = 0;
|
let mainPid = 0;
|
||||||
let systemdActiveRunning = false;
|
let systemdActiveRunning = false;
|
||||||
@ -353,22 +353,9 @@ async function compute(db, _peers) {
|
|||||||
httpLatencyMs,
|
httpLatencyMs,
|
||||||
httpError
|
httpError
|
||||||
};
|
};
|
||||||
await db.insert(hermesGatewayHealth).values(row);
|
return row;
|
||||||
return {
|
|
||||||
ts: row.ts,
|
|
||||||
alive: row.alive,
|
|
||||||
mainPid: row.mainPid,
|
|
||||||
rssBytes: row.rssBytes,
|
|
||||||
cpuPercent: row.cpuPercent,
|
|
||||||
uptimeSec: row.uptimeSec,
|
|
||||||
activeSessions: row.activeSessions,
|
|
||||||
childProcessCount: row.childProcessCount,
|
|
||||||
httpOk: row.httpOk,
|
|
||||||
httpStatusCode: row.httpStatusCode,
|
|
||||||
httpLatencyMs: row.httpLatencyMs,
|
|
||||||
httpError: row.httpError
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
compute
|
compute,
|
||||||
|
hermesGatewayHealth as table
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { execFile } from "node:child_process";
|
import { execFile } from "node:child_process";
|
||||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
export { hermesGatewayHealth as table } from "./schema.ts";
|
||||||
import { hermesGatewayHealth } from "./schema.ts";
|
|
||||||
|
|
||||||
/** Keep subprocess deadlines slightly under typical sense timeout (30s). */
|
/** Keep subprocess deadlines slightly under typical sense timeout (30s). */
|
||||||
const EXEC_TIMEOUT_MS = 25_000;
|
const EXEC_TIMEOUT_MS = 25_000;
|
||||||
@ -315,7 +314,7 @@ async function countDirectChildren(mainPid: number): Promise<number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
export async function compute(_signal: AbortSignal) {
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
|
|
||||||
let mainPid = 0;
|
let mainPid = 0;
|
||||||
@ -405,20 +404,5 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
httpError,
|
httpError,
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(hermesGatewayHealth).values(row);
|
return row;
|
||||||
|
|
||||||
return {
|
|
||||||
ts: row.ts,
|
|
||||||
alive: row.alive,
|
|
||||||
mainPid: row.mainPid,
|
|
||||||
rssBytes: row.rssBytes,
|
|
||||||
cpuPercent: row.cpuPercent,
|
|
||||||
uptimeSec: row.uptimeSec,
|
|
||||||
activeSessions: row.activeSessions,
|
|
||||||
childProcessCount: row.childProcessCount,
|
|
||||||
httpOk: row.httpOk,
|
|
||||||
httpStatusCode: row.httpStatusCode,
|
|
||||||
httpLatencyMs: row.httpLatencyMs,
|
|
||||||
httpError: row.httpError,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,7 +61,7 @@ async function aggregateJsonlFile(filePath, cutoffMs, nowMs) {
|
|||||||
}
|
}
|
||||||
return { user, assistant, tool, fileHadActivity };
|
return { user, assistant, tool, fileHadActivity };
|
||||||
}
|
}
|
||||||
async function compute(db, _peers) {
|
async function compute(_signal) {
|
||||||
const nowMs = Date.now();
|
const nowMs = Date.now();
|
||||||
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
||||||
const ts = nowMs;
|
const ts = nowMs;
|
||||||
@ -102,17 +102,9 @@ async function compute(db, _peers) {
|
|||||||
activeSessions,
|
activeSessions,
|
||||||
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS
|
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS
|
||||||
};
|
};
|
||||||
await db.insert(hermesSessionMessageStats).values(row);
|
return row;
|
||||||
return {
|
|
||||||
ts: row.ts,
|
|
||||||
totalUserMessages: row.totalUserMessages,
|
|
||||||
totalAssistantMessages: row.totalAssistantMessages,
|
|
||||||
totalToolMessages: row.totalToolMessages,
|
|
||||||
totalMessages: row.totalMessages,
|
|
||||||
activeSessions: row.activeSessions,
|
|
||||||
measurementWindowSeconds: row.measurementWindowSeconds
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
compute
|
compute,
|
||||||
|
hermesSessionMessageStats as table
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,7 @@ import { readdir } from "node:fs/promises";
|
|||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { createInterface } from "node:readline";
|
import { createInterface } from "node:readline";
|
||||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
export { hermesSessionMessageStats as table } from "./schema.ts";
|
||||||
import { hermesSessionMessageStats } from "./schema.ts";
|
|
||||||
|
|
||||||
const MEASUREMENT_WINDOW_MS = 900_000;
|
const MEASUREMENT_WINDOW_MS = 900_000;
|
||||||
const MEASUREMENT_WINDOW_SECONDS = 900;
|
const MEASUREMENT_WINDOW_SECONDS = 900;
|
||||||
@ -64,7 +63,7 @@ async function aggregateJsonlFile(filePath: string, cutoffMs: number, nowMs: num
|
|||||||
return { user, assistant, tool, fileHadActivity };
|
return { user, assistant, tool, fileHadActivity };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
export async function compute(_signal: AbortSignal) {
|
||||||
const nowMs = Date.now();
|
const nowMs = Date.now();
|
||||||
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
const cutoffMs = nowMs - MEASUREMENT_WINDOW_MS;
|
||||||
const ts = nowMs;
|
const ts = nowMs;
|
||||||
@ -114,15 +113,5 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS,
|
measurementWindowSeconds: MEASUREMENT_WINDOW_SECONDS,
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(hermesSessionMessageStats).values(row);
|
return row;
|
||||||
|
|
||||||
return {
|
|
||||||
ts: row.ts,
|
|
||||||
totalUserMessages: row.totalUserMessages,
|
|
||||||
totalAssistantMessages: row.totalAssistantMessages,
|
|
||||||
totalToolMessages: row.totalToolMessages,
|
|
||||||
totalMessages: row.totalMessages,
|
|
||||||
activeSessions: row.activeSessions,
|
|
||||||
measurementWindowSeconds: row.measurementWindowSeconds,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ function parseSockstat(content) {
|
|||||||
}
|
}
|
||||||
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
||||||
}
|
}
|
||||||
async function compute(db, _peers) {
|
async function compute(_signal) {
|
||||||
const [load1, load5, load15] = loadavg();
|
const [load1, load5, load15] = loadavg();
|
||||||
const memTotal = totalmem();
|
const memTotal = totalmem();
|
||||||
const memFree = freemem();
|
const memFree = freemem();
|
||||||
@ -80,7 +80,7 @@ async function compute(db, _peers) {
|
|||||||
}
|
}
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const uptimeSec = Math.round(uptime());
|
const uptimeSec = Math.round(uptime());
|
||||||
await db.insert(snapshots).values({
|
return {
|
||||||
ts,
|
ts,
|
||||||
cpuLoad1m: load1,
|
cpuLoad1m: load1,
|
||||||
cpuLoad5m: load5,
|
cpuLoad5m: load5,
|
||||||
@ -98,15 +98,9 @@ async function compute(db, _peers) {
|
|||||||
tcpTw: tcp.tcpTw,
|
tcpTw: tcp.tcpTw,
|
||||||
tcpAlloc: tcp.tcpAlloc,
|
tcpAlloc: tcp.tcpAlloc,
|
||||||
tcpMemPages: tcp.tcpMemPages
|
tcpMemPages: tcp.tcpMemPages
|
||||||
});
|
|
||||||
return {
|
|
||||||
cpu: { load1m: load1, load5m: load5, load15m: load15 },
|
|
||||||
memory: { totalMB: memTotalMB, usedMB: memUsedMB, usedPct: memUsedPct },
|
|
||||||
disk: { totalGB: diskTotalGB, usedGB: diskUsedGB, usedPct: diskUsedPct },
|
|
||||||
tcp: { socketsUsed: tcp.socketsUsed, inuse: tcp.tcpInuse, orphan: tcp.tcpOrphan, tw: tcp.tcpTw, alloc: tcp.tcpAlloc, memPages: tcp.tcpMemPages },
|
|
||||||
uptimeSec
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
compute
|
compute,
|
||||||
|
snapshots as table
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { loadavg, totalmem, freemem, uptime } from "node:os";
|
import { loadavg, totalmem, freemem, uptime } from "node:os";
|
||||||
import { execSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
export { snapshots as table } from "./schema.ts";
|
||||||
import { snapshots } from "./schema.ts";
|
|
||||||
|
|
||||||
const SOCKSTAT_PATH = "/proc/net/sockstat";
|
const SOCKSTAT_PATH = "/proc/net/sockstat";
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ function parseSockstat(content: string): SockstatResult {
|
|||||||
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
return { socketsUsed, tcpInuse, tcpOrphan, tcpTw, tcpAlloc, tcpMemPages };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
export async function compute(_signal: AbortSignal) {
|
||||||
const [load1, load5, load15] = loadavg();
|
const [load1, load5, load15] = loadavg();
|
||||||
|
|
||||||
const memTotal = totalmem();
|
const memTotal = totalmem();
|
||||||
@ -73,7 +72,7 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const uptimeSec = Math.round(uptime());
|
const uptimeSec = Math.round(uptime());
|
||||||
|
|
||||||
await db.insert(snapshots).values({
|
return {
|
||||||
ts, cpuLoad1m: load1, cpuLoad5m: load5, cpuLoad15m: load15,
|
ts, cpuLoad1m: load1, cpuLoad5m: load5, cpuLoad15m: load15,
|
||||||
memTotalMB, memUsedMB, memUsedPct,
|
memTotalMB, memUsedMB, memUsedPct,
|
||||||
diskTotalGB, diskUsedGB, diskUsedPct,
|
diskTotalGB, diskUsedGB, diskUsedPct,
|
||||||
@ -84,13 +83,5 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
tcpTw: tcp.tcpTw,
|
tcpTw: tcp.tcpTw,
|
||||||
tcpAlloc: tcp.tcpAlloc,
|
tcpAlloc: tcp.tcpAlloc,
|
||||||
tcpMemPages: tcp.tcpMemPages,
|
tcpMemPages: tcp.tcpMemPages,
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
cpu: { load1m: load1, load5m: load5, load15m: load15 },
|
|
||||||
memory: { totalMB: memTotalMB, usedMB: memUsedMB, usedPct: memUsedPct },
|
|
||||||
disk: { totalGB: diskTotalGB, usedGB: diskUsedGB, usedPct: diskUsedPct },
|
|
||||||
tcp: { socketsUsed: tcp.socketsUsed, inuse: tcp.tcpInuse, orphan: tcp.tcpOrphan, tw: tcp.tcpTw, alloc: tcp.tcpAlloc, memPages: tcp.tcpMemPages },
|
|
||||||
uptimeSec,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ var workerProcessMetrics = sqliteTable("worker_process_metrics", {
|
|||||||
function round2(n) {
|
function round2(n) {
|
||||||
return Math.round(n * 100) / 100;
|
return Math.round(n * 100) / 100;
|
||||||
}
|
}
|
||||||
async function compute(db, _peers) {
|
async function compute(_signal) {
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const pid = process.pid;
|
const pid = process.pid;
|
||||||
const uptimeSec = process.uptime();
|
const uptimeSec = process.uptime();
|
||||||
@ -29,16 +29,9 @@ async function compute(db, _peers) {
|
|||||||
rssMB,
|
rssMB,
|
||||||
externalMB
|
externalMB
|
||||||
};
|
};
|
||||||
await db.insert(workerProcessMetrics).values(row);
|
return row;
|
||||||
return {
|
|
||||||
ts: row.ts,
|
|
||||||
pid: row.pid,
|
|
||||||
uptimeSec: row.uptimeSec,
|
|
||||||
heapUsedMB: row.heapUsedMB,
|
|
||||||
rssMB: row.rssMB,
|
|
||||||
externalMB: row.externalMB
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
compute
|
compute,
|
||||||
|
workerProcessMetrics as table
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
export { workerProcessMetrics as table } from "./schema.ts";
|
||||||
import { workerProcessMetrics } from "./schema.ts";
|
|
||||||
|
|
||||||
function round2(n: number): number {
|
function round2(n: number): number {
|
||||||
return Math.round(n * 100) / 100;
|
return Math.round(n * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
export async function compute(_signal: AbortSignal) {
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
const pid = process.pid;
|
const pid = process.pid;
|
||||||
const uptimeSec = process.uptime();
|
const uptimeSec = process.uptime();
|
||||||
@ -23,14 +22,5 @@ export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
|||||||
externalMB,
|
externalMB,
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(workerProcessMetrics).values(row);
|
return row;
|
||||||
|
|
||||||
return {
|
|
||||||
ts: row.ts,
|
|
||||||
pid: row.pid,
|
|
||||||
uptimeSec: row.uptimeSec,
|
|
||||||
heapUsedMB: row.heapUsedMB,
|
|
||||||
rssMB: row.rssMB,
|
|
||||||
externalMB: row.externalMB,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user