@f5-sales-demo/xcsh-stats 21.13.1 → 21.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/db.ts +16 -5
- package/src/index.ts +2 -2
- package/src/server.ts +10 -23
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh-stats",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.14.0",
|
|
5
5
|
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"fmt": "biome format --write ."
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@f5-sales-demo/pi-ai": "21.
|
|
42
|
-
"@f5-sales-demo/pi-utils": "21.
|
|
41
|
+
"@f5-sales-demo/pi-ai": "21.14.0",
|
|
42
|
+
"@f5-sales-demo/pi-utils": "21.14.0",
|
|
43
43
|
"@tailwindcss/node": "^4.2",
|
|
44
44
|
"chart.js": "^4.5",
|
|
45
45
|
"date-fns": "~4.4.0",
|
package/src/db.ts
CHANGED
|
@@ -101,6 +101,16 @@ export function setFileOffset(sessionFile: string, offset: number, lastModified:
|
|
|
101
101
|
stmt.run(sessionFile, offset, lastModified);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function normalizeUsageCost(cost: MessageStats["usage"]["cost"]): MessageStats["usage"]["cost"] {
|
|
105
|
+
const partial = cost as Partial<MessageStats["usage"]["cost"]>;
|
|
106
|
+
const input = partial.input ?? 0;
|
|
107
|
+
const output = partial.output ?? 0;
|
|
108
|
+
const cacheRead = partial.cacheRead ?? 0;
|
|
109
|
+
const cacheWrite = partial.cacheWrite ?? 0;
|
|
110
|
+
const total = partial.total ?? input + output + cacheRead + cacheWrite;
|
|
111
|
+
return { input, output, cacheRead, cacheWrite, total };
|
|
112
|
+
}
|
|
113
|
+
|
|
104
114
|
/**
|
|
105
115
|
* Insert message stats into the database.
|
|
106
116
|
*/
|
|
@@ -119,6 +129,7 @@ export function insertMessageStats(stats: MessageStats[]): number {
|
|
|
119
129
|
let inserted = 0;
|
|
120
130
|
const insert = db.transaction(() => {
|
|
121
131
|
for (const s of stats) {
|
|
132
|
+
const cost = normalizeUsageCost(s.usage.cost);
|
|
122
133
|
const result = stmt.run(
|
|
123
134
|
s.sessionFile,
|
|
124
135
|
s.entryId,
|
|
@@ -137,11 +148,11 @@ export function insertMessageStats(stats: MessageStats[]): number {
|
|
|
137
148
|
s.usage.cacheWrite,
|
|
138
149
|
s.usage.totalTokens,
|
|
139
150
|
s.usage.premiumRequests ?? 0,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
cost.input,
|
|
152
|
+
cost.output,
|
|
153
|
+
cost.cacheRead,
|
|
154
|
+
cost.cacheWrite,
|
|
155
|
+
cost.total,
|
|
145
156
|
);
|
|
146
157
|
if (result.changes > 0) inserted++;
|
|
147
158
|
}
|
package/src/index.ts
CHANGED
|
@@ -130,8 +130,8 @@ Examples:
|
|
|
130
130
|
|
|
131
131
|
// Start server
|
|
132
132
|
const port = parseInt(values.port || "3847", 10);
|
|
133
|
-
const { port: actualPort } = await startServer(port);
|
|
134
|
-
console.log(`Dashboard available at: http
|
|
133
|
+
const { hostname, port: actualPort } = await startServer(port);
|
|
134
|
+
console.log(`Dashboard available at: http://${hostname}:${actualPort}`);
|
|
135
135
|
console.log("Press Ctrl+C to stop\n");
|
|
136
136
|
|
|
137
137
|
// Keep process running
|
package/src/server.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
} from "./aggregator";
|
|
13
13
|
import embeddedClientArchiveTxt from "./embedded-client.generated.txt";
|
|
14
14
|
|
|
15
|
+
const STATS_DASHBOARD_HOSTNAME = "127.0.0.1";
|
|
16
|
+
|
|
15
17
|
const getEmbeddedClientArchive = (() => {
|
|
16
18
|
const txt = embeddedClientArchiveTxt.replaceAll(/[\s\r\n]/g, "").trim();
|
|
17
19
|
if (!txt) return null;
|
|
@@ -157,6 +159,8 @@ const ensureClientBuild = async () => {
|
|
|
157
159
|
</body>
|
|
158
160
|
</html>`;
|
|
159
161
|
|
|
162
|
+
// STATIC_DIR is derived only from import.meta.dir; no HTML input reaches this filesystem path.
|
|
163
|
+
// nosemgrep: javascript.lang.security.audit.unknown-value-with-script-tag.unknown-value-with-script-tag
|
|
160
164
|
await Bun.write(path.join(STATIC_DIR, "index.html"), indexHtml);
|
|
161
165
|
};
|
|
162
166
|
|
|
@@ -244,24 +248,18 @@ async function handleStatic(requestPath: string): Promise<Response> {
|
|
|
244
248
|
/**
|
|
245
249
|
* Start the HTTP server.
|
|
246
250
|
*/
|
|
247
|
-
export async function startServer(port = 3847): Promise<{ port: number; stop: () => void }> {
|
|
251
|
+
export async function startServer(port = 3847): Promise<{ hostname: string; port: number; stop: () => void }> {
|
|
248
252
|
await ensureClientBuild();
|
|
249
253
|
|
|
250
254
|
const server = Bun.serve({
|
|
251
255
|
port,
|
|
256
|
+
hostname: STATS_DASHBOARD_HOSTNAME,
|
|
252
257
|
async fetch(req) {
|
|
253
258
|
const url = new URL(req.url);
|
|
254
259
|
const path = url.pathname;
|
|
255
260
|
|
|
256
|
-
// CORS headers for local development
|
|
257
|
-
const corsHeaders = {
|
|
258
|
-
"Access-Control-Allow-Origin": "*",
|
|
259
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
260
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
|
261
|
-
};
|
|
262
|
-
|
|
263
261
|
if (req.method === "OPTIONS") {
|
|
264
|
-
return new Response(null, {
|
|
262
|
+
return new Response(null, { status: 204 });
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
try {
|
|
@@ -273,27 +271,16 @@ export async function startServer(port = 3847): Promise<{ port: number; stop: ()
|
|
|
273
271
|
response = await handleStatic(path);
|
|
274
272
|
}
|
|
275
273
|
|
|
276
|
-
|
|
277
|
-
const headers = new Headers(response.headers);
|
|
278
|
-
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
279
|
-
headers.set(key, value);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return new Response(response.body, {
|
|
283
|
-
status: response.status,
|
|
284
|
-
headers,
|
|
285
|
-
});
|
|
274
|
+
return response;
|
|
286
275
|
} catch (error) {
|
|
287
276
|
console.error("Server error:", error);
|
|
288
|
-
return Response.json(
|
|
289
|
-
{ error: error instanceof Error ? error.message : "Unknown error" },
|
|
290
|
-
{ status: 500, headers: corsHeaders },
|
|
291
|
-
);
|
|
277
|
+
return Response.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 });
|
|
292
278
|
}
|
|
293
279
|
},
|
|
294
280
|
});
|
|
295
281
|
|
|
296
282
|
return {
|
|
283
|
+
hostname: STATS_DASHBOARD_HOSTNAME,
|
|
297
284
|
port: server.port ?? port,
|
|
298
285
|
stop: () => server.stop(),
|
|
299
286
|
};
|