codex-lb 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- app/core/clients/proxy.py +33 -3
- app/core/config/settings.py +1 -0
- app/core/openai/requests.py +21 -3
- app/core/openai/v1_requests.py +148 -0
- app/db/models.py +3 -3
- app/main.py +1 -0
- app/modules/accounts/repository.py +4 -1
- app/modules/proxy/api.py +36 -0
- app/modules/proxy/service.py +29 -0
- app/modules/request_logs/api.py +61 -7
- app/modules/request_logs/repository.py +128 -16
- app/modules/request_logs/schemas.py +11 -2
- app/modules/request_logs/service.py +97 -20
- app/modules/usage/updater.py +58 -26
- app/static/index.css +1400 -347
- app/static/index.html +627 -415
- app/static/index.js +409 -42
- codex_lb-0.4.0.dist-info/METADATA +172 -0
- {codex_lb-0.3.0.dist-info → codex_lb-0.4.0.dist-info}/RECORD +22 -22
- app/static/7.css +0 -1409
- codex_lb-0.3.0.dist-info/METADATA +0 -108
- {codex_lb-0.3.0.dist-info → codex_lb-0.4.0.dist-info}/WHEEL +0 -0
- {codex_lb-0.3.0.dist-info → codex_lb-0.4.0.dist-info}/entry_points.txt +0 -0
- {codex_lb-0.3.0.dist-info → codex_lb-0.4.0.dist-info}/licenses/LICENSE +0 -0
app/static/index.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
usageSummary: "/api/usage/summary",
|
|
13
13
|
usageWindow: (window) =>
|
|
14
14
|
`/api/usage/window?window=${encodeURIComponent(window)}`,
|
|
15
|
-
requestLogs:
|
|
15
|
+
requestLogs: "/api/request-logs",
|
|
16
|
+
requestLogOptions: "/api/request-logs/options",
|
|
16
17
|
oauthStart: "/api/oauth/start",
|
|
17
18
|
oauthStatus: "/api/oauth/status",
|
|
18
19
|
oauthComplete: "/api/oauth/complete",
|
|
@@ -79,9 +80,20 @@
|
|
|
79
80
|
ok: "active",
|
|
80
81
|
rate_limit: "limited",
|
|
81
82
|
quota: "exceeded",
|
|
82
|
-
error: "
|
|
83
|
+
error: "error",
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
const MODEL_OPTION_DELIMITER = ":::";
|
|
87
|
+
|
|
88
|
+
const createDefaultRequestFilters = () => ({
|
|
89
|
+
search: "",
|
|
90
|
+
timeframe: "all",
|
|
91
|
+
accountIds: [],
|
|
92
|
+
modelOptions: [],
|
|
93
|
+
statuses: [],
|
|
94
|
+
minCost: "",
|
|
95
|
+
});
|
|
96
|
+
|
|
85
97
|
const KNOWN_PLAN_TYPES = new Set([
|
|
86
98
|
"free",
|
|
87
99
|
"plus",
|
|
@@ -180,11 +192,16 @@
|
|
|
180
192
|
minimumFractionDigits: 2,
|
|
181
193
|
maximumFractionDigits: 2,
|
|
182
194
|
});
|
|
183
|
-
const
|
|
195
|
+
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
184
196
|
hour: "2-digit",
|
|
185
197
|
minute: "2-digit",
|
|
186
198
|
second: "2-digit",
|
|
187
199
|
});
|
|
200
|
+
const dateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
201
|
+
year: "numeric",
|
|
202
|
+
month: "2-digit",
|
|
203
|
+
day: "2-digit",
|
|
204
|
+
});
|
|
188
205
|
|
|
189
206
|
const createEmptyDashboardData = () => ({
|
|
190
207
|
lastSyncAt: "",
|
|
@@ -373,9 +390,12 @@
|
|
|
373
390
|
const formatTimeLong = (iso) => {
|
|
374
391
|
const date = parseDate(iso);
|
|
375
392
|
if (!date) {
|
|
376
|
-
return "--";
|
|
393
|
+
return { time: "--", date: "--" };
|
|
377
394
|
}
|
|
378
|
-
return
|
|
395
|
+
return {
|
|
396
|
+
time: timeFormatter.format(date),
|
|
397
|
+
date: dateFormatter.format(date),
|
|
398
|
+
};
|
|
379
399
|
};
|
|
380
400
|
|
|
381
401
|
const formatRelative = (ms) => {
|
|
@@ -506,6 +526,33 @@
|
|
|
506
526
|
};
|
|
507
527
|
const routingLabel = (strategy) => ROUTING_LABELS[strategy] || "unknown";
|
|
508
528
|
const errorLabel = (code) => ERROR_LABELS[code] || "--";
|
|
529
|
+
const calculateProgressClass = (status, remainingPercent) => {
|
|
530
|
+
if (status === "exceeded") return "error";
|
|
531
|
+
if (status === "paused" || status === "deactivated") return "";
|
|
532
|
+
const percent = toNumber(remainingPercent) || 0;
|
|
533
|
+
if (percent <= 20) return "error";
|
|
534
|
+
if (percent <= 50) return "limited";
|
|
535
|
+
return "success";
|
|
536
|
+
};
|
|
537
|
+
const calculateProgressTextClass = (status, remainingPercent) => {
|
|
538
|
+
const cls = calculateProgressClass(status, remainingPercent);
|
|
539
|
+
return cls ? `text-${cls}` : "";
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const calculateTextUsageClass = (status, remainingPercent) => {
|
|
543
|
+
if (status === "exceeded") return "error";
|
|
544
|
+
// For text, we show usage color even if paused. Only deactivated is plain.
|
|
545
|
+
if (status === "deactivated") return "";
|
|
546
|
+
const percent = toNumber(remainingPercent) || 0;
|
|
547
|
+
if (percent <= 20) return "error";
|
|
548
|
+
if (percent <= 50) return "limited";
|
|
549
|
+
return "success";
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const calculateTextUsageTextClass = (status, remainingPercent) => {
|
|
553
|
+
const cls = calculateTextUsageClass(status, remainingPercent);
|
|
554
|
+
return cls ? `text-${cls}` : "";
|
|
555
|
+
};
|
|
509
556
|
const progressClass = (status) => PROGRESS_CLASS_BY_STATUS[status] || "";
|
|
510
557
|
|
|
511
558
|
const normalizeSearchInput = (value) =>
|
|
@@ -741,7 +788,7 @@
|
|
|
741
788
|
typeof windowMinutes === "number" && windowMinutes > 0
|
|
742
789
|
? windowMinutes / 60
|
|
743
790
|
: 24 * 7;
|
|
744
|
-
return
|
|
791
|
+
return numeric / hours;
|
|
745
792
|
};
|
|
746
793
|
|
|
747
794
|
const countByStatus = (accounts) =>
|
|
@@ -937,9 +984,9 @@
|
|
|
937
984
|
const entries =
|
|
938
985
|
window.key === "primary"
|
|
939
986
|
? applySecondaryExhaustedToPrimary(
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
987
|
+
rawEntries,
|
|
988
|
+
secondaryExhaustedAccounts,
|
|
989
|
+
)
|
|
943
990
|
: rawEntries;
|
|
944
991
|
const remaining =
|
|
945
992
|
hasPrimaryAdjustments
|
|
@@ -954,21 +1001,40 @@
|
|
|
954
1001
|
window.key,
|
|
955
1002
|
);
|
|
956
1003
|
const gradient = buildDonutGradient(items, capacity);
|
|
957
|
-
const legendItems = items.map((item) =>
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1004
|
+
const legendItems = items.map((item) => {
|
|
1005
|
+
const percent = item.remainingPercent;
|
|
1006
|
+
let valueClass = "success";
|
|
1007
|
+
if (percent <= 20) {
|
|
1008
|
+
valueClass = "error";
|
|
1009
|
+
} else if (percent <= 50) {
|
|
1010
|
+
valueClass = "limited";
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
label: truncateText(item.label, 28),
|
|
1014
|
+
fullLabel: item.label,
|
|
1015
|
+
detailLabel: "Remaining",
|
|
1016
|
+
detailValue: formatPercent(item.remainingPercent),
|
|
1017
|
+
valueClass,
|
|
1018
|
+
color: item.color,
|
|
1019
|
+
};
|
|
1020
|
+
});
|
|
1021
|
+
if (capacity > 0) {
|
|
964
1022
|
const consumedPercent = Math.min(
|
|
965
1023
|
100,
|
|
966
1024
|
Math.max(0, (consumed / capacity) * 100),
|
|
967
1025
|
);
|
|
1026
|
+
let consumedClass = "success";
|
|
1027
|
+
if (consumedPercent >= 80) {
|
|
1028
|
+
consumedClass = "error";
|
|
1029
|
+
} else if (consumedPercent >= 50) {
|
|
1030
|
+
consumedClass = "limited";
|
|
1031
|
+
}
|
|
968
1032
|
legendItems.push({
|
|
969
1033
|
label: "Consumed",
|
|
1034
|
+
fullLabel: "Consumed",
|
|
970
1035
|
detailLabel: "",
|
|
971
1036
|
detailValue: formatPercent(consumedPercent),
|
|
1037
|
+
valueClass: consumedClass,
|
|
972
1038
|
color: CONSUMED_COLOR,
|
|
973
1039
|
});
|
|
974
1040
|
}
|
|
@@ -995,7 +1061,7 @@
|
|
|
995
1061
|
},
|
|
996
1062
|
remaining: remainingRounded,
|
|
997
1063
|
remainingText: formatPercent(secondaryRemaining),
|
|
998
|
-
progressClass:
|
|
1064
|
+
progressClass: calculateProgressClass(account.status, secondaryRemaining),
|
|
999
1065
|
marquee: account.status === "deactivated",
|
|
1000
1066
|
meta: formatQuotaResetMeta(
|
|
1001
1067
|
account.resetAtSecondary,
|
|
@@ -1009,6 +1075,9 @@
|
|
|
1009
1075
|
const rawError = request.errorMessage || request.errorCode || "";
|
|
1010
1076
|
const accountLabel = formatAccountLabel(request.accountId, accounts);
|
|
1011
1077
|
const modelLabel = formatModelLabel(request.model, request.reasoningEffort);
|
|
1078
|
+
const totalTokens = formatCompactNumber(request.tokens);
|
|
1079
|
+
const cachedInputTokens = toNumber(request.cachedInputTokens);
|
|
1080
|
+
const cachedTokens = cachedInputTokens > 0 ? formatCompactNumber(cachedInputTokens) : null;
|
|
1012
1081
|
return {
|
|
1013
1082
|
key: `${request.requestId}-${request.timestamp}`,
|
|
1014
1083
|
requestId: request.requestId,
|
|
@@ -1019,10 +1088,16 @@
|
|
|
1019
1088
|
class: requestStatusClass(request.status),
|
|
1020
1089
|
label: requestStatusLabel(request.status),
|
|
1021
1090
|
},
|
|
1022
|
-
tokens:
|
|
1091
|
+
tokens: {
|
|
1092
|
+
total: totalTokens,
|
|
1093
|
+
cached: cachedTokens,
|
|
1094
|
+
},
|
|
1095
|
+
tokensTooltip: formatTokensWithCached(request.tokens, request.cachedInputTokens),
|
|
1023
1096
|
cost: formatCurrency(request.cost),
|
|
1024
1097
|
error: rawError ? truncateText(rawError, 80) : "--",
|
|
1025
1098
|
errorTitle: rawError,
|
|
1099
|
+
isTruncated: rawError.length > 20,
|
|
1100
|
+
isErrorPlaceholder: !rawError,
|
|
1026
1101
|
};
|
|
1027
1102
|
});
|
|
1028
1103
|
|
|
@@ -1191,8 +1266,107 @@
|
|
|
1191
1266
|
const fetchUsageWindow = async (window) =>
|
|
1192
1267
|
fetchJson(API_ENDPOINTS.usageWindow(window), `usage window (${window})`);
|
|
1193
1268
|
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1269
|
+
const parseMinCostUsd = (value) => {
|
|
1270
|
+
if (value === null || value === undefined) {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
const asString = String(value).trim();
|
|
1274
|
+
if (!asString) {
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
const numeric = Number(asString);
|
|
1278
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
const buildSinceIsoFromTimeframe = (timeframe) => {
|
|
1282
|
+
const now = Date.now();
|
|
1283
|
+
if (timeframe === "1h") {
|
|
1284
|
+
return new Date(now - 60 * 60 * 1000).toISOString();
|
|
1285
|
+
}
|
|
1286
|
+
if (timeframe === "24h") {
|
|
1287
|
+
return new Date(now - 24 * 60 * 60 * 1000).toISOString();
|
|
1288
|
+
}
|
|
1289
|
+
if (timeframe === "7d") {
|
|
1290
|
+
return new Date(now - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
1291
|
+
}
|
|
1292
|
+
return null;
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
const buildRequestLogsQueryParams = ({ filters, pagination }) => {
|
|
1296
|
+
const params = {
|
|
1297
|
+
limit: pagination?.limit,
|
|
1298
|
+
offset: pagination?.offset,
|
|
1299
|
+
};
|
|
1300
|
+
if (filters?.search) {
|
|
1301
|
+
params.search = filters.search;
|
|
1302
|
+
}
|
|
1303
|
+
if (Array.isArray(filters?.accountIds) && filters.accountIds.length) {
|
|
1304
|
+
params.accountId = filters.accountIds.filter(Boolean);
|
|
1305
|
+
}
|
|
1306
|
+
if (Array.isArray(filters?.modelOptions) && filters.modelOptions.length) {
|
|
1307
|
+
params.modelOption = filters.modelOptions.filter(Boolean);
|
|
1308
|
+
}
|
|
1309
|
+
if (Array.isArray(filters?.statuses) && filters.statuses.length) {
|
|
1310
|
+
params.status = filters.statuses.filter(Boolean);
|
|
1311
|
+
}
|
|
1312
|
+
const since = buildSinceIsoFromTimeframe(filters?.timeframe);
|
|
1313
|
+
if (since) {
|
|
1314
|
+
params.since = since;
|
|
1315
|
+
}
|
|
1316
|
+
return params;
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
const applyClientSideRequestFilters = (requests, filters) => {
|
|
1320
|
+
const minCostUsd = parseMinCostUsd(filters?.minCost);
|
|
1321
|
+
if (minCostUsd === null) {
|
|
1322
|
+
return requests;
|
|
1323
|
+
}
|
|
1324
|
+
return (requests || []).filter((entry) => (entry.cost || 0) >= minCostUsd);
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
const buildQueryString = (params) => {
|
|
1328
|
+
const searchParams = new URLSearchParams();
|
|
1329
|
+
if (!params || typeof params !== "object") {
|
|
1330
|
+
return "";
|
|
1331
|
+
}
|
|
1332
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1333
|
+
if (value === null || value === undefined) {
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (Array.isArray(value)) {
|
|
1337
|
+
const filtered = value
|
|
1338
|
+
.map((item) => String(item ?? "").trim())
|
|
1339
|
+
.filter(Boolean);
|
|
1340
|
+
for (const item of filtered) {
|
|
1341
|
+
searchParams.append(key, item);
|
|
1342
|
+
}
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (typeof value === "string" && value.trim() === "") {
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
searchParams.append(key, String(value));
|
|
1349
|
+
}
|
|
1350
|
+
return searchParams.toString();
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
const fetchRequestLogs = async (params) => {
|
|
1354
|
+
const query = buildQueryString(params);
|
|
1355
|
+
return fetchJson(
|
|
1356
|
+
query ? `${API_ENDPOINTS.requestLogs}?${query}` : API_ENDPOINTS.requestLogs,
|
|
1357
|
+
"request logs",
|
|
1358
|
+
);
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
const fetchRequestLogOptions = async (params) => {
|
|
1362
|
+
const query = buildQueryString(params);
|
|
1363
|
+
return fetchJson(
|
|
1364
|
+
query
|
|
1365
|
+
? `${API_ENDPOINTS.requestLogOptions}?${query}`
|
|
1366
|
+
: API_ENDPOINTS.requestLogOptions,
|
|
1367
|
+
"request log options",
|
|
1368
|
+
);
|
|
1369
|
+
};
|
|
1196
1370
|
|
|
1197
1371
|
const normalizeSettingsPayload = (payload) => ({
|
|
1198
1372
|
stickyThreadsEnabled: Boolean(payload?.stickyThreadsEnabled),
|
|
@@ -1212,6 +1386,24 @@
|
|
|
1212
1386
|
ui: createUiConfig(),
|
|
1213
1387
|
dashboardData: createEmptyDashboardData(),
|
|
1214
1388
|
dashboard: createEmptyDashboardView(),
|
|
1389
|
+
|
|
1390
|
+
filtersDraft: createDefaultRequestFilters(),
|
|
1391
|
+
filtersApplied: createDefaultRequestFilters(),
|
|
1392
|
+
pagination: {
|
|
1393
|
+
limit: 25,
|
|
1394
|
+
offset: 0,
|
|
1395
|
+
},
|
|
1396
|
+
recentRequestsState: {
|
|
1397
|
+
isLoading: false,
|
|
1398
|
+
error: "",
|
|
1399
|
+
},
|
|
1400
|
+
requestLogOptions: {
|
|
1401
|
+
accountIds: [],
|
|
1402
|
+
modelOptions: [],
|
|
1403
|
+
isLoading: false,
|
|
1404
|
+
error: "",
|
|
1405
|
+
},
|
|
1406
|
+
|
|
1215
1407
|
settings: {
|
|
1216
1408
|
stickyThreadsEnabled: false,
|
|
1217
1409
|
preferEarlierResetAccounts: false,
|
|
@@ -1259,12 +1451,19 @@
|
|
|
1259
1451
|
isLoading: true,
|
|
1260
1452
|
hasInitialized: false,
|
|
1261
1453
|
refreshPromise: null,
|
|
1454
|
+
|
|
1262
1455
|
async init() {
|
|
1263
1456
|
if (this.hasInitialized) {
|
|
1264
1457
|
return;
|
|
1265
1458
|
}
|
|
1266
1459
|
this.hasInitialized = true;
|
|
1267
1460
|
this.view = getViewFromPath(window.location.pathname);
|
|
1461
|
+
|
|
1462
|
+
this.$watch("pagination.limit", () => {
|
|
1463
|
+
this.pagination.offset = 0;
|
|
1464
|
+
this.refreshRequests();
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1268
1467
|
await this.loadData();
|
|
1269
1468
|
this.syncTitle();
|
|
1270
1469
|
this.syncUrl(true);
|
|
@@ -1283,6 +1482,115 @@
|
|
|
1283
1482
|
}
|
|
1284
1483
|
});
|
|
1285
1484
|
},
|
|
1485
|
+
|
|
1486
|
+
async refreshRequests() {
|
|
1487
|
+
try {
|
|
1488
|
+
this.recentRequestsState.isLoading = true;
|
|
1489
|
+
this.recentRequestsState.error = "";
|
|
1490
|
+
const params = buildRequestLogsQueryParams({
|
|
1491
|
+
filters: this.filtersApplied,
|
|
1492
|
+
pagination: this.pagination,
|
|
1493
|
+
});
|
|
1494
|
+
const requestsData = await fetchRequestLogs(params);
|
|
1495
|
+
const normalized = normalizeRequestLogsPayload(requestsData);
|
|
1496
|
+
const requests = applyClientSideRequestFilters(
|
|
1497
|
+
normalized,
|
|
1498
|
+
this.filtersApplied,
|
|
1499
|
+
);
|
|
1500
|
+
|
|
1501
|
+
this.dashboardData.recentRequests = requests;
|
|
1502
|
+
|
|
1503
|
+
this.dashboard = buildDashboardView(this);
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
this.recentRequestsState.error =
|
|
1506
|
+
err?.message || "Failed to refresh requests.";
|
|
1507
|
+
console.error("Failed to refresh requests:", err);
|
|
1508
|
+
} finally {
|
|
1509
|
+
this.recentRequestsState.isLoading = false;
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
|
|
1513
|
+
applyFilters() {
|
|
1514
|
+
this.pagination.offset = 0;
|
|
1515
|
+
const accountIds = Array.isArray(this.filtersDraft.accountIds)
|
|
1516
|
+
? [...new Set(this.filtersDraft.accountIds.map(String).filter(Boolean))]
|
|
1517
|
+
: [];
|
|
1518
|
+
const modelOptions = Array.isArray(this.filtersDraft.modelOptions)
|
|
1519
|
+
? [...new Set(this.filtersDraft.modelOptions.map(String).filter(Boolean))]
|
|
1520
|
+
: [];
|
|
1521
|
+
const statuses = Array.isArray(this.filtersDraft.statuses)
|
|
1522
|
+
? [...new Set(this.filtersDraft.statuses.map(String).filter(Boolean))]
|
|
1523
|
+
: [];
|
|
1524
|
+
this.filtersApplied = {
|
|
1525
|
+
...createDefaultRequestFilters(),
|
|
1526
|
+
...this.filtersDraft,
|
|
1527
|
+
accountIds,
|
|
1528
|
+
modelOptions,
|
|
1529
|
+
statuses,
|
|
1530
|
+
};
|
|
1531
|
+
this.refreshRequests();
|
|
1532
|
+
},
|
|
1533
|
+
|
|
1534
|
+
resetFilters() {
|
|
1535
|
+
this.filtersDraft = createDefaultRequestFilters();
|
|
1536
|
+
this.applyFilters();
|
|
1537
|
+
},
|
|
1538
|
+
|
|
1539
|
+
accountFilterLabel(accountId) {
|
|
1540
|
+
return formatAccountLabel(accountId, this.accounts.rows);
|
|
1541
|
+
},
|
|
1542
|
+
|
|
1543
|
+
modelOptionValue(option) {
|
|
1544
|
+
const model = String(option?.model || "").trim();
|
|
1545
|
+
const effort = String(option?.reasoningEffort || "").trim();
|
|
1546
|
+
return `${model}${MODEL_OPTION_DELIMITER}${effort}`;
|
|
1547
|
+
},
|
|
1548
|
+
|
|
1549
|
+
modelOptionLabel(option) {
|
|
1550
|
+
return formatModelLabel(option?.model, option?.reasoningEffort);
|
|
1551
|
+
},
|
|
1552
|
+
|
|
1553
|
+
toggleMultiSelectValue(listKey, rawValue) {
|
|
1554
|
+
const value = String(rawValue ?? "").trim();
|
|
1555
|
+
if (!value) {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const current = Array.isArray(this.filtersDraft[listKey])
|
|
1559
|
+
? [...this.filtersDraft[listKey]]
|
|
1560
|
+
: [];
|
|
1561
|
+
const index = current.indexOf(value);
|
|
1562
|
+
if (index >= 0) {
|
|
1563
|
+
current.splice(index, 1);
|
|
1564
|
+
} else {
|
|
1565
|
+
current.push(value);
|
|
1566
|
+
}
|
|
1567
|
+
this.filtersDraft = { ...this.filtersDraft, [listKey]: current };
|
|
1568
|
+
},
|
|
1569
|
+
|
|
1570
|
+
multiSelectSummary(values, emptyLabel, singularLabel, pluralLabel) {
|
|
1571
|
+
const list = Array.isArray(values)
|
|
1572
|
+
? values.map((value) => String(value ?? "").trim()).filter(Boolean)
|
|
1573
|
+
: [];
|
|
1574
|
+
if (list.length === 0) {
|
|
1575
|
+
return emptyLabel;
|
|
1576
|
+
}
|
|
1577
|
+
if (list.length === 1) {
|
|
1578
|
+
return `1 ${singularLabel}`;
|
|
1579
|
+
}
|
|
1580
|
+
const plural = pluralLabel ? String(pluralLabel) : `${singularLabel}s`;
|
|
1581
|
+
return `${list.length} ${plural}`;
|
|
1582
|
+
},
|
|
1583
|
+
|
|
1584
|
+
timeframeLabel(value) {
|
|
1585
|
+
const labels = {
|
|
1586
|
+
all: "All time",
|
|
1587
|
+
"1h": "Last 1h",
|
|
1588
|
+
"24h": "Last 24h",
|
|
1589
|
+
"7d": "Last 7d",
|
|
1590
|
+
};
|
|
1591
|
+
return labels[value] || "All time";
|
|
1592
|
+
},
|
|
1593
|
+
|
|
1286
1594
|
async loadData() {
|
|
1287
1595
|
try {
|
|
1288
1596
|
await this.refreshAll({ silent: true });
|
|
@@ -1305,22 +1613,32 @@
|
|
|
1305
1613
|
return this.refreshPromise;
|
|
1306
1614
|
}
|
|
1307
1615
|
const { preferredId, silent = false } = options;
|
|
1616
|
+
this.requestLogOptions.isLoading = true;
|
|
1617
|
+
this.requestLogOptions.error = "";
|
|
1308
1618
|
this.refreshPromise = (async () => {
|
|
1619
|
+
const params = buildRequestLogsQueryParams({
|
|
1620
|
+
filters: this.filtersApplied,
|
|
1621
|
+
pagination: this.pagination,
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1309
1624
|
const [
|
|
1310
1625
|
accountsResult,
|
|
1311
1626
|
summaryResult,
|
|
1312
1627
|
primaryResult,
|
|
1313
1628
|
secondaryResult,
|
|
1314
1629
|
requestLogsResult,
|
|
1630
|
+
requestLogOptionsResult,
|
|
1315
1631
|
settingsResult,
|
|
1316
1632
|
] = await Promise.allSettled([
|
|
1317
1633
|
fetchAccounts(),
|
|
1318
1634
|
fetchUsageSummary(),
|
|
1319
1635
|
fetchUsageWindow("primary"),
|
|
1320
1636
|
fetchUsageWindow("secondary"),
|
|
1321
|
-
fetchRequestLogs(
|
|
1637
|
+
fetchRequestLogs(params),
|
|
1638
|
+
fetchRequestLogOptions({}),
|
|
1322
1639
|
fetchSettings(),
|
|
1323
1640
|
]);
|
|
1641
|
+
|
|
1324
1642
|
const errors = [];
|
|
1325
1643
|
if (accountsResult.status !== "fulfilled") {
|
|
1326
1644
|
throw accountsResult.reason;
|
|
@@ -1352,6 +1670,21 @@
|
|
|
1352
1670
|
errors.push(requestLogsResult.reason);
|
|
1353
1671
|
}
|
|
1354
1672
|
|
|
1673
|
+
if (requestLogOptionsResult.status === "fulfilled") {
|
|
1674
|
+
const payload = requestLogOptionsResult.value;
|
|
1675
|
+
this.requestLogOptions.accountIds = Array.isArray(payload?.accountIds)
|
|
1676
|
+
? payload.accountIds
|
|
1677
|
+
: [];
|
|
1678
|
+
this.requestLogOptions.modelOptions = Array.isArray(payload?.modelOptions)
|
|
1679
|
+
? payload.modelOptions
|
|
1680
|
+
: [];
|
|
1681
|
+
} else {
|
|
1682
|
+
this.requestLogOptions.error =
|
|
1683
|
+
requestLogOptionsResult.reason?.message ||
|
|
1684
|
+
"Failed to load request log options.";
|
|
1685
|
+
errors.push(requestLogOptionsResult.reason);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1355
1688
|
const settings =
|
|
1356
1689
|
settingsResult.status === "fulfilled" ? settingsResult.value : null;
|
|
1357
1690
|
if (settingsResult.status === "rejected") {
|
|
@@ -1369,7 +1702,10 @@
|
|
|
1369
1702
|
summary,
|
|
1370
1703
|
primaryUsage,
|
|
1371
1704
|
secondaryUsage,
|
|
1372
|
-
requestLogs
|
|
1705
|
+
requestLogs: applyClientSideRequestFilters(
|
|
1706
|
+
requestLogs,
|
|
1707
|
+
this.filtersApplied,
|
|
1708
|
+
),
|
|
1373
1709
|
settings,
|
|
1374
1710
|
},
|
|
1375
1711
|
preferredId,
|
|
@@ -1391,6 +1727,7 @@
|
|
|
1391
1727
|
await this.refreshPromise;
|
|
1392
1728
|
} finally {
|
|
1393
1729
|
this.refreshPromise = null;
|
|
1730
|
+
this.requestLogOptions.isLoading = false;
|
|
1394
1731
|
}
|
|
1395
1732
|
},
|
|
1396
1733
|
applyData(data, preferredId) {
|
|
@@ -1700,12 +2037,33 @@
|
|
|
1700
2037
|
window.open(this.authDialog.verificationUrl, "_blank", "noopener");
|
|
1701
2038
|
}
|
|
1702
2039
|
},
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
2040
|
+
calculateProgressClass(status, remainingPercent) {
|
|
2041
|
+
return calculateProgressClass(status, remainingPercent);
|
|
2042
|
+
},
|
|
2043
|
+
calculateProgressTextClass(status, remainingPercent) {
|
|
2044
|
+
return calculateProgressTextClass(status, remainingPercent);
|
|
2045
|
+
},
|
|
2046
|
+
async copyToClipboard(value, label, e) {
|
|
2047
|
+
if (!value) return;
|
|
2048
|
+
|
|
2049
|
+
// Localized feedback in the button
|
|
2050
|
+
let btn = null;
|
|
2051
|
+
let originalText = "";
|
|
2052
|
+
if (e && e.target) {
|
|
2053
|
+
btn = e.target.tagName === "BUTTON" ? e.target : e.target.closest("button");
|
|
2054
|
+
if (btn) {
|
|
2055
|
+
originalText = btn.textContent;
|
|
2056
|
+
btn.textContent = "Copied!";
|
|
2057
|
+
btn.classList.add("copy-success");
|
|
2058
|
+
window.setTimeout(() => {
|
|
2059
|
+
btn.textContent = originalText;
|
|
2060
|
+
btn.classList.remove("copy-success");
|
|
2061
|
+
}, 4000);
|
|
2062
|
+
}
|
|
1706
2063
|
}
|
|
2064
|
+
|
|
1707
2065
|
try {
|
|
1708
|
-
if (navigator.clipboard
|
|
2066
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1709
2067
|
await navigator.clipboard.writeText(value);
|
|
1710
2068
|
} else {
|
|
1711
2069
|
const textarea = document.createElement("textarea");
|
|
@@ -1718,29 +2076,26 @@
|
|
|
1718
2076
|
document.execCommand("copy");
|
|
1719
2077
|
document.body.removeChild(textarea);
|
|
1720
2078
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
window.setTimeout(() => {
|
|
1725
|
-
if (this.authDialog.statusLabel === `${label} copied.`) {
|
|
1726
|
-
this.authDialog.statusLabel = previousLabel;
|
|
1727
|
-
}
|
|
1728
|
-
}, 2000);
|
|
1729
|
-
} else {
|
|
2079
|
+
|
|
2080
|
+
// Only show message box if auth dialog is not open and button feedback wasn't possible
|
|
2081
|
+
if (!this.authDialog.open && !btn) {
|
|
1730
2082
|
this.openMessageBox({
|
|
1731
2083
|
tone: "success",
|
|
1732
2084
|
title: "Copied",
|
|
1733
2085
|
message: `${label} copied to clipboard.`,
|
|
1734
2086
|
});
|
|
1735
2087
|
}
|
|
1736
|
-
} catch (
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2088
|
+
} catch (err) {
|
|
2089
|
+
console.error("Clipboard error:", err);
|
|
2090
|
+
if (btn) {
|
|
2091
|
+
btn.textContent = "Failed";
|
|
2092
|
+
btn.classList.remove("copy-success");
|
|
2093
|
+
window.setTimeout(() => { btn.textContent = originalText; }, 2000);
|
|
2094
|
+
} else if (!this.authDialog.open) {
|
|
1740
2095
|
this.openMessageBox({
|
|
1741
2096
|
tone: "error",
|
|
1742
2097
|
title: "Copy failed",
|
|
1743
|
-
message:
|
|
2098
|
+
message: `Could not copy ${label}.`,
|
|
1744
2099
|
});
|
|
1745
2100
|
}
|
|
1746
2101
|
}
|
|
@@ -1896,15 +2251,19 @@
|
|
|
1896
2251
|
},
|
|
1897
2252
|
get statusItems() {
|
|
1898
2253
|
const lastSync = formatTimeLong(this.dashboardData.lastSyncAt);
|
|
2254
|
+
const lastSyncLabel =
|
|
2255
|
+
lastSync && lastSync.time && lastSync.time !== "--"
|
|
2256
|
+
? `${lastSync.time} · ${lastSync.date}`
|
|
2257
|
+
: "--";
|
|
1899
2258
|
const items =
|
|
1900
2259
|
this.view === "accounts"
|
|
1901
2260
|
? [
|
|
1902
2261
|
`Selection: ${this.accounts.selectedId || "--"}`,
|
|
1903
2262
|
`Rotation: ${this.dashboardData.routing?.rotationEnabled ? "enabled" : "disabled"}`,
|
|
1904
|
-
`Last sync: ${
|
|
2263
|
+
`Last sync: ${lastSyncLabel}`,
|
|
1905
2264
|
]
|
|
1906
2265
|
: [
|
|
1907
|
-
`Last sync: ${
|
|
2266
|
+
`Last sync: ${lastSyncLabel}`,
|
|
1908
2267
|
`Routing: ${routingLabel(this.dashboardData.routing?.strategy)}`,
|
|
1909
2268
|
`Backend: ${this.backendPath}`,
|
|
1910
2269
|
];
|
|
@@ -2084,6 +2443,7 @@
|
|
|
2084
2443
|
statusLabel,
|
|
2085
2444
|
requestStatusLabel,
|
|
2086
2445
|
requestStatusClass,
|
|
2446
|
+
calculateTextUsageTextClass,
|
|
2087
2447
|
progressClass,
|
|
2088
2448
|
planLabel,
|
|
2089
2449
|
routingLabel,
|
|
@@ -2100,7 +2460,14 @@
|
|
|
2100
2460
|
formatQuotaResetLabel,
|
|
2101
2461
|
formatAccessTokenLabel,
|
|
2102
2462
|
formatRefreshTokenLabel,
|
|
2463
|
+
formatAccessTokenLabel,
|
|
2464
|
+
formatRefreshTokenLabel,
|
|
2103
2465
|
formatIdTokenLabel,
|
|
2466
|
+
theme: localStorage.getItem('theme') || 'dark',
|
|
2467
|
+
toggleTheme() {
|
|
2468
|
+
this.theme = this.theme === 'dark' ? 'light' : 'dark';
|
|
2469
|
+
localStorage.setItem('theme', this.theme);
|
|
2470
|
+
},
|
|
2104
2471
|
}));
|
|
2105
2472
|
};
|
|
2106
2473
|
|