Skip to content

Commit 3fc5975

Browse files
The system executed 5 runs with a 0% success rate, an average response time of 0.393 ms, and an error rate of 0%. (#1103)
Signed-off-by: NAYANAR <[email protected]> Co-authored-by: NAYANAR <[email protected]>
1 parent a357c64 commit 3fc5975

File tree

1 file changed

+222
-70
lines changed

1 file changed

+222
-70
lines changed

mcpgateway/static/admin.js

Lines changed: 222 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,6 @@ function createKPISection(kpiData) {
11011101
const section = document.createElement("div");
11021102
section.className = "grid grid-cols-1 md:grid-cols-4 gap-4";
11031103

1104-
// Define KPI indicators with safe configuration
11051104
const kpis = [
11061105
{
11071106
key: "totalExecutions",
@@ -1114,26 +1113,35 @@ function createKPISection(kpiData) {
11141113
label: "Success Rate",
11151114
icon: "✅",
11161115
color: "green",
1117-
suffix: "%",
11181116
},
11191117
{
11201118
key: "avgResponseTime",
11211119
label: "Avg Response Time",
11221120
icon: "⚡",
11231121
color: "yellow",
1124-
suffix: "ms",
1125-
},
1126-
{
1127-
key: "errorRate",
1128-
label: "Error Rate",
1129-
icon: "❌",
1130-
color: "red",
1131-
suffix: "%",
11321122
},
1123+
{ key: "errorRate", label: "Error Rate", icon: "❌", color: "red" },
11331124
];
11341125

11351126
kpis.forEach((kpi) => {
1136-
const value = kpiData[kpi.key] ?? "N/A";
1127+
let value = kpiData[kpi.key];
1128+
if (value === null || value === undefined || value === "N/A") {
1129+
value = "N/A";
1130+
} else {
1131+
if (kpi.key === "avgResponseTime") {
1132+
// ensure numeric then 3 decimals + unit
1133+
value = isNaN(Number(value))
1134+
? "N/A"
1135+
: Number(value).toFixed(3) + " ms";
1136+
} else if (
1137+
kpi.key === "successRate" ||
1138+
kpi.key === "errorRate"
1139+
) {
1140+
value = String(value) + "%";
1141+
} else {
1142+
value = String(value);
1143+
}
1144+
}
11371145

11381146
const kpiCard = document.createElement("div");
11391147
kpiCard.className = `bg-white rounded-lg shadow p-4 border-l-4 border-${kpi.color}-500 dark:bg-gray-800`;
@@ -1150,8 +1158,7 @@ function createKPISection(kpiData) {
11501158

11511159
const valueSpan = document.createElement("div");
11521160
valueSpan.className = `text-2xl font-bold text-${kpi.color}-600`;
1153-
valueSpan.textContent =
1154-
(value === "N/A" ? "N/A" : String(value)) + (kpi.suffix || "");
1161+
valueSpan.textContent = value;
11551162

11561163
const labelSpan = document.createElement("div");
11571164
labelSpan.className = "text-sm text-gray-500 dark:text-gray-400";
@@ -1166,73 +1173,205 @@ function createKPISection(kpiData) {
11661173
});
11671174

11681175
return section;
1169-
} catch (error) {
1170-
console.error("Error creating KPI section:", error);
1171-
return document.createElement("div"); // Safe fallback
1176+
} catch (err) {
1177+
console.error("Error creating KPI section:", err);
1178+
return document.createElement("div");
11721179
}
11731180
}
11741181

11751182
/**
11761183
* SECURITY: Extract and calculate KPI data with validation
11771184
*/
1185+
function formatValue(value, key) {
1186+
if (value === null || value === undefined || value === "N/A") {
1187+
return "N/A";
1188+
}
1189+
1190+
if (key === "avgResponseTime") {
1191+
return isNaN(Number(value)) ? "N/A" : Number(value).toFixed(3) + " ms";
1192+
}
1193+
1194+
if (key === "successRate" || key === "errorRate") {
1195+
return `${value}%`;
1196+
}
1197+
1198+
if (typeof value === "number" && Number.isNaN(value)) {
1199+
return "N/A";
1200+
}
1201+
1202+
return String(value).trim() === "" ? "N/A" : String(value);
1203+
}
1204+
11781205
function extractKPIData(data) {
11791206
try {
1180-
const kpiData = {};
1181-
1182-
// Initialize calculation variables
11831207
let totalExecutions = 0;
11841208
let totalSuccessful = 0;
11851209
let totalFailed = 0;
1186-
const responseTimes = [];
1210+
let weightedResponseSum = 0;
11871211

1188-
// Process each category safely
1189-
const categories = [
1190-
"tools",
1191-
"resources",
1192-
"prompts",
1193-
"gateways",
1194-
"servers",
1212+
const categoryKeys = [
1213+
["tools", "Tools Metrics", "Tools", "tools_metrics"],
1214+
[
1215+
"resources",
1216+
"Resources Metrics",
1217+
"Resources",
1218+
"resources_metrics",
1219+
],
1220+
["prompts", "Prompts Metrics", "Prompts", "prompts_metrics"],
1221+
["servers", "Servers Metrics", "Servers", "servers_metrics"],
1222+
["gateways", "Gateways Metrics", "Gateways", "gateways_metrics"],
1223+
[
1224+
"virtualServers",
1225+
"Virtual Servers",
1226+
"VirtualServers",
1227+
"virtual_servers",
1228+
],
11951229
];
1196-
categories.forEach((category) => {
1197-
if (data[category]) {
1198-
const categoryData = data[category];
1199-
totalExecutions += Number(categoryData.totalExecutions || 0);
1200-
totalSuccessful += Number(
1201-
categoryData.successfulExecutions || 0,
1202-
);
1203-
totalFailed += Number(categoryData.failedExecutions || 0);
12041230

1205-
if (
1206-
categoryData.avgResponseTime &&
1207-
categoryData.avgResponseTime !== "N/A"
1208-
) {
1209-
responseTimes.push(Number(categoryData.avgResponseTime));
1231+
categoryKeys.forEach((aliases) => {
1232+
let categoryData = null;
1233+
for (const key of aliases) {
1234+
if (data && data[key]) {
1235+
categoryData = data[key];
1236+
break;
12101237
}
12111238
}
1239+
if (!categoryData) {
1240+
return;
1241+
}
1242+
1243+
// Build a lowercase-key map so "Successful Executions" and "successfulExecutions" both match
1244+
const normalized = {};
1245+
Object.entries(categoryData).forEach(([k, v]) => {
1246+
normalized[k.toString().trim().toLowerCase()] = v;
1247+
});
1248+
1249+
const executions = Number(
1250+
normalized["total executions"] ??
1251+
normalized.totalexecutions ??
1252+
normalized.execution_count ??
1253+
normalized["execution-count"] ??
1254+
normalized.executions ??
1255+
normalized.total_executions ??
1256+
0,
1257+
);
1258+
1259+
const successful = Number(
1260+
normalized["successful executions"] ??
1261+
normalized.successfulexecutions ??
1262+
normalized.successful ??
1263+
normalized.successful_executions ??
1264+
0,
1265+
);
1266+
1267+
const failed = Number(
1268+
normalized["failed executions"] ??
1269+
normalized.failedexecutions ??
1270+
normalized.failed ??
1271+
normalized.failed_executions ??
1272+
0,
1273+
);
1274+
1275+
const avgResponseRaw =
1276+
normalized["average response time"] ??
1277+
normalized.avgresponsetime ??
1278+
normalized.avg_response_time ??
1279+
normalized.avgresponsetime ??
1280+
null;
1281+
1282+
totalExecutions += Number.isNaN(executions) ? 0 : executions;
1283+
totalSuccessful += Number.isNaN(successful) ? 0 : successful;
1284+
totalFailed += Number.isNaN(failed) ? 0 : failed;
1285+
1286+
if (
1287+
avgResponseRaw !== null &&
1288+
avgResponseRaw !== undefined &&
1289+
avgResponseRaw !== "N/A" &&
1290+
!Number.isNaN(Number(avgResponseRaw)) &&
1291+
executions > 0
1292+
) {
1293+
weightedResponseSum += executions * Number(avgResponseRaw);
1294+
}
12121295
});
12131296

1214-
// Calculate safe aggregate metrics
1215-
kpiData.totalExecutions = totalExecutions;
1216-
kpiData.successRate =
1297+
const avgResponseTime =
1298+
totalExecutions > 0 && weightedResponseSum > 0
1299+
? weightedResponseSum / totalExecutions
1300+
: null;
1301+
1302+
const successRate =
12171303
totalExecutions > 0
12181304
? Math.round((totalSuccessful / totalExecutions) * 100)
12191305
: 0;
1220-
kpiData.errorRate =
1306+
1307+
const errorRate =
12211308
totalExecutions > 0
12221309
? Math.round((totalFailed / totalExecutions) * 100)
12231310
: 0;
1224-
kpiData.avgResponseTime =
1225-
responseTimes.length > 0
1226-
? Math.round(
1227-
responseTimes.reduce((a, b) => a + b, 0) /
1228-
responseTimes.length,
1229-
)
1230-
: "N/A";
1231-
1232-
return kpiData;
1233-
} catch (error) {
1234-
console.error("Error extracting KPI data:", error);
1235-
return {}; // Safe fallback
1311+
1312+
// Debug: show what we've read from the payload
1313+
console.log("KPI Totals:", {
1314+
totalExecutions,
1315+
totalSuccessful,
1316+
totalFailed,
1317+
successRate,
1318+
errorRate,
1319+
avgResponseTime,
1320+
});
1321+
1322+
return { totalExecutions, successRate, errorRate, avgResponseTime };
1323+
} catch (err) {
1324+
console.error("Error extracting KPI data:", err);
1325+
return {
1326+
totalExecutions: 0,
1327+
successRate: 0,
1328+
errorRate: 0,
1329+
avgResponseTime: null,
1330+
};
1331+
}
1332+
}
1333+
1334+
// eslint-disable-next-line no-unused-vars
1335+
function updateKPICards(kpiData) {
1336+
try {
1337+
if (!kpiData) {
1338+
return;
1339+
}
1340+
1341+
const idMap = {
1342+
"metrics-total-executions": formatValue(
1343+
kpiData.totalExecutions,
1344+
"totalExecutions",
1345+
),
1346+
"metrics-success-rate": formatValue(
1347+
kpiData.successRate,
1348+
"successRate",
1349+
),
1350+
"metrics-avg-response-time": formatValue(
1351+
kpiData.avgResponseTime,
1352+
"avgResponseTime",
1353+
),
1354+
"metrics-error-rate": formatValue(kpiData.errorRate, "errorRate"),
1355+
};
1356+
1357+
Object.entries(idMap).forEach(([id, value]) => {
1358+
const el = document.getElementById(id);
1359+
if (!el) {
1360+
return;
1361+
}
1362+
1363+
// If card has a `.value` span inside, update it, else update directly
1364+
const valueEl =
1365+
el.querySelector?.(".value") ||
1366+
el.querySelector?.(".kpi-value");
1367+
if (valueEl) {
1368+
valueEl.textContent = value;
1369+
} else {
1370+
el.textContent = value;
1371+
}
1372+
});
1373+
} catch (err) {
1374+
console.error("updateKPICards error:", err);
12361375
}
12371376
}
12381377

@@ -1389,26 +1528,39 @@ function formatLastUsed(timestamp) {
13891528
return "Never";
13901529
}
13911530

1392-
const date = new Date(timestamp);
1393-
const now = new Date();
1394-
const diffMs = now - date;
1395-
const diffMins = Math.floor(diffMs / 60000);
1396-
1397-
if (diffMins < 1) {
1398-
return "Just now";
1531+
let date;
1532+
if (typeof timestamp === "number" || /^\d+$/.test(timestamp)) {
1533+
const num = Number(timestamp);
1534+
date = new Date(num < 1e12 ? num * 1000 : num); // epoch seconds or ms
1535+
} else {
1536+
date = new Date(timestamp.endsWith("Z") ? timestamp : timestamp + "Z");
13991537
}
1400-
if (diffMins < 60) {
1401-
return `${diffMins} min ago`;
1538+
1539+
if (isNaN(date.getTime())) {
1540+
return "Never";
14021541
}
1403-
if (diffMins < 1440) {
1404-
return `${Math.floor(diffMins / 60)} hours ago`;
1542+
1543+
const now = Date.now();
1544+
const diff = now - date.getTime();
1545+
1546+
if (diff < 60 * 1000) {
1547+
return "Just now";
14051548
}
1406-
if (diffMins < 10080) {
1407-
return `${Math.floor(diffMins / 1440)} days ago`;
1549+
if (diff < 60 * 60 * 1000) {
1550+
return `${Math.floor(diff / 60000)} min ago`;
14081551
}
14091552

1410-
return date.toLocaleDateString();
1553+
return date.toLocaleString(undefined, {
1554+
year: "numeric",
1555+
month: "short",
1556+
day: "numeric",
1557+
hour: "2-digit",
1558+
minute: "2-digit",
1559+
hour12: true,
1560+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1561+
});
14111562
}
1563+
14121564
function createTopPerformersTable(entityType, data, isActive) {
14131565
const panel = document.createElement("div");
14141566
panel.id = `top-${entityType}-panel`;

0 commit comments

Comments
 (0)