feat: Claude subscription usage via OCPlatform gateway + System Monitor redesign
All checks were successful
Build & Deploy Dashboard / deploy (push) Successful in 1m17s

This commit is contained in:
Cosmo
2026-04-16 09:34:20 +00:00
parent 490149b669
commit 35f6456363
3 changed files with 287 additions and 55 deletions

View File

@@ -0,0 +1,119 @@
export const dynamic = "force-dynamic";
import { NextResponse } from "next/server";
const GATEWAY_URL = "ws://192.168.31.103:18789";
const GATEWAY_TOKEN = "c55292f854e8308c4fed926c40c3a8995a7213fde79fed72";
async function gatewayRequest(method: string, params: object = {}): Promise<unknown> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
ws.close();
reject(new Error("Gateway timeout"));
}, 8000);
const ws = new (require("ws"))(`${GATEWAY_URL}`);
let connected = false;
let reqId = 1;
ws.on("open", () => {});
ws.on("message", (data: Buffer) => {
try {
const msg = JSON.parse(data.toString());
// Auth challenge
if (msg.type === "event" && msg.event === "connect.challenge") {
ws.send(JSON.stringify({
type: "request",
id: String(reqId++),
method: "connect",
params: { token: GATEWAY_TOKEN },
}));
return;
}
// Connect response
if (msg.type === "response" && !connected) {
if (msg.ok) {
connected = true;
// Send actual request
ws.send(JSON.stringify({
type: "request",
id: String(reqId++),
method,
params,
}));
} else {
clearTimeout(timeout);
ws.close();
reject(new Error("Auth failed"));
}
return;
}
// Method response
if (msg.type === "response" && connected) {
clearTimeout(timeout);
ws.close();
if (msg.ok) {
resolve(msg.payload);
} else {
reject(new Error(msg.error || "Request failed"));
}
}
} catch {}
});
ws.on("error", (err: Error) => {
clearTimeout(timeout);
reject(err);
});
});
}
export async function GET() {
try {
const [statusResult, costResult] = await Promise.allSettled([
gatewayRequest("usage.status", {}),
gatewayRequest("usage.cost", {}),
]);
const status = statusResult.status === "fulfilled" ? statusResult.value : null;
const cost = costResult.status === "fulfilled" ? costResult.value : null;
// Извлечь данные Anthropic
const anthropic = (status as any)?.providers?.find((p: any) => p.provider === "anthropic");
const windows = anthropic?.windows || [];
const window5h = windows.find((w: any) => w.label?.includes("5") || w.label === "5h");
const windowWeek = windows.find((w: any) => w.label?.toLowerCase().includes("week") || w.label === "Week");
const windowSonnet = windows.find((w: any) => w.label?.toLowerCase().includes("sonnet"));
const todayCost = (cost as any)?.daily?.[0]?.totalCost || 0;
const todayTokens = (cost as any)?.daily?.[0]?.totalTokens || 0;
const totalCost = (cost as any)?.totals?.totalCost || 0;
return NextResponse.json({
ok: true,
anthropic: {
window5h: window5h || null,
windowWeek: windowWeek || null,
windowSonnet: windowSonnet || null,
error: anthropic?.error || null,
},
cost: {
today: todayCost,
todayTokens,
total: totalCost,
},
raw: { status, cost },
});
} catch (e) {
return NextResponse.json({
ok: false,
error: String(e),
anthropic: null,
cost: null,
});
}
}