pi-antigravity-rotator 1.4.0 → 1.4.5
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/CHANGELOG.md +14 -0
- package/README.md +8 -9
- package/package.json +1 -1
- package/src/dashboard.ts +148 -37
- package/src/rotator.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.4.5] - 2026-04-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Extended Token Views**: Added `2h`, `4h`, `8h`, and `12h` options to the Token Usage chart. The backend now retains up to 12 hours of minute-level resolution for accurate high-fidelity zooming.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Activity Heatmap Scaling**: Expanded the heatmap to cover the last 60 days. The grid is now responsive, taking up the full width of the screen without distorting cell proportions, and the Y-axis now orders hours naturally from 00 to 23.
|
|
12
|
+
- **Timezone Alignment**: X-axis labels on the Token Usage chart and the Heatmap now correctly reflect the browser's local time instead of UTC.
|
|
13
|
+
|
|
14
|
+
## [1.4.1] - 2026-04-28
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Export Data**: Added `CSV` and `JSON` export buttons to the Token Usage panel on the dashboard to easily download token metrics for external reporting.
|
|
18
|
+
|
|
5
19
|
## [1.4.0] - 2026-04-28
|
|
6
20
|
|
|
7
21
|
### Added
|
package/README.md
CHANGED
|
@@ -94,15 +94,14 @@ After starting the proxy, open `http://localhost:51200/dashboard` or `http://<yo
|
|
|
94
94
|
|
|
95
95
|
The dashboard shows:
|
|
96
96
|
|
|
97
|
-
- **
|
|
98
|
-
- **
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
- Re-enable button for disabled accounts
|
|
97
|
+
- **Top Status & Controls** -- Real-time routing state, uptime, requests, and PII masking toggle.
|
|
98
|
+
- **Token Usage & Savings** -- Interactive chart (`1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `7d`, `1m`) showing token consumption by model, with estimated USD savings and `CSV`/`JSON` export options.
|
|
99
|
+
- **Activity Heatmap** -- 60-day responsive GitHub-style contribution grid showing request intensity hour by hour.
|
|
100
|
+
- **Latency (p50/p95)** -- Real-time median and 95th percentile tracking for Time-to-First-Byte (TTFB) and Total Duration per model.
|
|
101
|
+
- **Quota Forecast** -- Predictive modeling showing when each model's quota will run out based on the current requests/hour burn rate.
|
|
102
|
+
- **Searchable Request Log** -- Live feed of the last 200 requests with exact timestamps, models, masked accounts, status codes, and latency.
|
|
103
|
+
- **Account Cards** -- Sorted by total quota. Shows status (`active`, `ready`, `cooldown`, `flagged`, `disabled`), served models, quota bars with timers, and precise error messages.
|
|
104
|
+
- **Operator Panels** -- "Attention Needed" summaries for quarantined accounts and a real-time event feed of rotator actions.
|
|
106
105
|
|
|
107
106
|

|
|
108
107
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-antigravity-rotator",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/dashboard.ts
CHANGED
|
@@ -835,11 +835,18 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
835
835
|
<div class="routing-panel state-stopped" id="routingHealth"></div>
|
|
836
836
|
|
|
837
837
|
<div class="routing-panel" id="tokenUsagePanel" style="margin-top:12px">
|
|
838
|
-
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
839
|
-
<strong>Token Usage</strong>
|
|
840
|
-
<div style="display:flex;gap:6px;align-items:center">
|
|
838
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;flex-wrap:wrap;gap:8px">
|
|
839
|
+
<strong style="min-width:max-content">Token Usage</strong>
|
|
840
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
|
841
841
|
<div id="tokenTotals" style="font-family:JetBrains Mono,monospace;font-size:0.85rem;color:var(--text-dim);margin-right:12px"></div>
|
|
842
|
+
<button class="btn-secondary btn-sm" onclick="exportData('csv')" title="Export CSV" style="padding:2px 6px">CSV</button>
|
|
843
|
+
<button class="btn-secondary btn-sm" onclick="exportData('json')" title="Export JSON" style="padding:2px 6px;margin-right:8px">JSON</button>
|
|
844
|
+
<div style="width:1px;height:16px;background:var(--border);margin-right:8px"></div>
|
|
842
845
|
<button class="btn-secondary btn-sm" onclick="setTokenView('1h')" id="tbtn-1h">1h</button>
|
|
846
|
+
<button class="btn-secondary btn-sm" onclick="setTokenView('2h')" id="tbtn-2h">2h</button>
|
|
847
|
+
<button class="btn-secondary btn-sm" onclick="setTokenView('4h')" id="tbtn-4h">4h</button>
|
|
848
|
+
<button class="btn-secondary btn-sm" onclick="setTokenView('8h')" id="tbtn-8h">8h</button>
|
|
849
|
+
<button class="btn-secondary btn-sm" onclick="setTokenView('12h')" id="tbtn-12h">12h</button>
|
|
843
850
|
<button class="btn-secondary btn-sm" onclick="setTokenView('1d')" id="tbtn-1d">1d</button>
|
|
844
851
|
<button class="btn-secondary btn-sm" onclick="setTokenView('7d')" id="tbtn-7d">7d</button>
|
|
845
852
|
<button class="btn-secondary btn-sm" onclick="setTokenView('1m')" id="tbtn-1m">1m</button>
|
|
@@ -863,7 +870,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
863
870
|
|
|
864
871
|
<div class="routing-panel" id="heatmapPanel" style="margin-top:12px;display:none">
|
|
865
872
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
|
866
|
-
<strong>Activity Heatmap (last
|
|
873
|
+
<strong>Activity Heatmap (last 60d)</strong>
|
|
867
874
|
<span style="color:var(--text-dim);font-size:0.75rem">rows: hour · cols: day</span>
|
|
868
875
|
</div>
|
|
869
876
|
<div id="heatmapGrid"></div>
|
|
@@ -1200,15 +1207,54 @@ function formatTokenCount(n) {
|
|
|
1200
1207
|
|
|
1201
1208
|
window.__tokenView = '1h';
|
|
1202
1209
|
|
|
1210
|
+
function exportData(format) {
|
|
1211
|
+
if (!window.__lastData || !window.__lastData.tokenUsage) return;
|
|
1212
|
+
var usage = window.__lastData.tokenUsage;
|
|
1213
|
+
|
|
1214
|
+
if (format === 'json') {
|
|
1215
|
+
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(usage, null, 2));
|
|
1216
|
+
var a = document.createElement('a');
|
|
1217
|
+
a.href = dataStr;
|
|
1218
|
+
a.download = "rotator-token-usage.json";
|
|
1219
|
+
a.click();
|
|
1220
|
+
} else if (format === 'csv') {
|
|
1221
|
+
var csv = "Tier,Period,Model,InputTokens,OutputTokens,Requests\\n";
|
|
1222
|
+
['months', 'days', 'hours', 'minutes'].forEach(function(tier) {
|
|
1223
|
+
(usage[tier] || []).forEach(function(b) {
|
|
1224
|
+
if (!b.byModel) return;
|
|
1225
|
+
Object.keys(b.byModel).forEach(function(m) {
|
|
1226
|
+
var d = b.byModel[m];
|
|
1227
|
+
csv += tier + "," + b.period + "," + m + "," + d.inputTokens + "," + d.outputTokens + "," + d.requests + "\\n";
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
});
|
|
1231
|
+
var dataStrCSV = "data:text/csv;charset=utf-8," + encodeURIComponent(csv);
|
|
1232
|
+
var a2 = document.createElement('a');
|
|
1233
|
+
a2.href = dataStrCSV;
|
|
1234
|
+
a2.download = "rotator-token-usage.csv";
|
|
1235
|
+
a2.click();
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1203
1239
|
function setTokenView(view) {
|
|
1204
1240
|
window.__tokenView = view;
|
|
1205
1241
|
refresh();
|
|
1206
1242
|
}
|
|
1207
1243
|
|
|
1208
1244
|
function formatBucketLabel(period, view) {
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1245
|
+
try {
|
|
1246
|
+
if (view.endsWith('h') && view !== '1d') {
|
|
1247
|
+
var d;
|
|
1248
|
+
if (period.length === 16) d = new Date(period + ':00Z');
|
|
1249
|
+
else d = new Date(period);
|
|
1250
|
+
if (!isNaN(d.getTime())) {
|
|
1251
|
+
if (view === '1h') return ':' + String(d.getMinutes()).padStart(2, '0');
|
|
1252
|
+
return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
if (view === '1d') return period.slice(11, 13) + 'h';
|
|
1256
|
+
if (view === '7d' || view === '1m') return period.slice(5, 10);
|
|
1257
|
+
} catch(e) {}
|
|
1212
1258
|
return period;
|
|
1213
1259
|
}
|
|
1214
1260
|
|
|
@@ -1220,7 +1266,7 @@ function renderTokenChart(tokenUsage) {
|
|
|
1220
1266
|
var view = window.__tokenView || '1h';
|
|
1221
1267
|
|
|
1222
1268
|
// Highlight active button
|
|
1223
|
-
['1h', '1d', '7d', '1m'].forEach(function(v) {
|
|
1269
|
+
['1h', '2h', '4h', '8h', '12h', '1d', '7d', '1m'].forEach(function(v) {
|
|
1224
1270
|
var btn = document.getElementById('tbtn-' + v);
|
|
1225
1271
|
if (btn) btn.className = 'btn-secondary btn-sm' + (v === view ? ' active' : '');
|
|
1226
1272
|
});
|
|
@@ -1230,11 +1276,12 @@ function renderTokenChart(tokenUsage) {
|
|
|
1230
1276
|
return;
|
|
1231
1277
|
}
|
|
1232
1278
|
|
|
1233
|
-
// Helper: merge buckets into a map by
|
|
1234
|
-
function mergeBucketsBy(sources,
|
|
1279
|
+
// Helper: merge buckets into a map by a grouping key
|
|
1280
|
+
function mergeBucketsBy(sources, keyFn, limit) {
|
|
1235
1281
|
var map = {};
|
|
1236
1282
|
sources.forEach(function(b) {
|
|
1237
|
-
var key = b.period
|
|
1283
|
+
var key = keyFn(b.period);
|
|
1284
|
+
if (!key) return;
|
|
1238
1285
|
if (!map[key]) map[key] = { period: key, inputTokens: 0, outputTokens: 0, requests: 0, byModel: {} };
|
|
1239
1286
|
map[key].inputTokens += b.inputTokens;
|
|
1240
1287
|
map[key].outputTokens += b.outputTokens;
|
|
@@ -1249,19 +1296,50 @@ function renderTokenChart(tokenUsage) {
|
|
|
1249
1296
|
return Object.keys(map).sort().map(function(k) { return map[k]; }).slice(-limit);
|
|
1250
1297
|
}
|
|
1251
1298
|
|
|
1299
|
+
function getLocalKey(periodStr, type) {
|
|
1300
|
+
try {
|
|
1301
|
+
var d;
|
|
1302
|
+
if (periodStr.length === 10) d = new Date(periodStr + 'T00:00:00Z');
|
|
1303
|
+
else if (periodStr.length === 13) d = new Date(periodStr + ':00:00Z');
|
|
1304
|
+
else if (periodStr.length === 16) d = new Date(periodStr + ':00Z');
|
|
1305
|
+
else d = new Date(periodStr);
|
|
1306
|
+
if (isNaN(d.getTime())) return periodStr;
|
|
1307
|
+
|
|
1308
|
+
var y = d.getFullYear();
|
|
1309
|
+
var mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
1310
|
+
var da = String(d.getDate()).padStart(2, '0');
|
|
1311
|
+
var h = String(d.getHours()).padStart(2, '0');
|
|
1312
|
+
var mi = d.getMinutes();
|
|
1313
|
+
|
|
1314
|
+
if (type === 'day') return y + '-' + mo + '-' + da;
|
|
1315
|
+
if (type === 'hour') return y + '-' + mo + '-' + da + 'T' + h;
|
|
1316
|
+
if (type === '5min') return y + '-' + mo + '-' + da + 'T' + h + ':' + String(Math.floor(mi/5)*5).padStart(2, '0');
|
|
1317
|
+
if (type === '4min') return y + '-' + mo + '-' + da + 'T' + h + ':' + String(Math.floor(mi/4)*4).padStart(2, '0');
|
|
1318
|
+
if (type === '2min') return y + '-' + mo + '-' + da + 'T' + h + ':' + String(Math.floor(mi/2)*2).padStart(2, '0');
|
|
1319
|
+
} catch(e) {}
|
|
1320
|
+
return periodStr;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1252
1323
|
var allTiers = (tokenUsage.months || []).concat(tokenUsage.days || []).concat(tokenUsage.hours || []).concat(tokenUsage.minutes || []);
|
|
1253
1324
|
|
|
1254
1325
|
// Pick tier based on view
|
|
1255
1326
|
var buckets;
|
|
1256
1327
|
if (view === '1h') {
|
|
1257
1328
|
buckets = (tokenUsage.minutes || []).slice(-60);
|
|
1329
|
+
} else if (view === '2h') {
|
|
1330
|
+
buckets = (tokenUsage.minutes || []).slice(-120);
|
|
1331
|
+
} else if (view === '4h') {
|
|
1332
|
+
buckets = mergeBucketsBy((tokenUsage.minutes || []), function(p) { return getLocalKey(p, '2min'); }, 120);
|
|
1333
|
+
} else if (view === '8h') {
|
|
1334
|
+
buckets = mergeBucketsBy((tokenUsage.minutes || []), function(p) { return getLocalKey(p, '4min'); }, 120);
|
|
1335
|
+
} else if (view === '12h') {
|
|
1336
|
+
buckets = mergeBucketsBy((tokenUsage.minutes || []), function(p) { return getLocalKey(p, '5min'); }, 144);
|
|
1258
1337
|
} else if (view === '1d') {
|
|
1259
|
-
|
|
1260
|
-
buckets = mergeBucketsBy((tokenUsage.hours || []).concat(tokenUsage.minutes || []), 13, 24);
|
|
1338
|
+
buckets = mergeBucketsBy((tokenUsage.hours || []).concat(tokenUsage.minutes || []), function(p) { return getLocalKey(p, 'hour'); }, 24);
|
|
1261
1339
|
} else if (view === '7d') {
|
|
1262
|
-
buckets = mergeBucketsBy(allTiers,
|
|
1340
|
+
buckets = mergeBucketsBy(allTiers, function(p) { return getLocalKey(p, 'day'); }, 7);
|
|
1263
1341
|
} else {
|
|
1264
|
-
buckets = mergeBucketsBy(allTiers,
|
|
1342
|
+
buckets = mergeBucketsBy(allTiers, function(p) { return getLocalKey(p, 'day'); }, 30);
|
|
1265
1343
|
}
|
|
1266
1344
|
|
|
1267
1345
|
if (!buckets || buckets.length === 0) {
|
|
@@ -1287,14 +1365,17 @@ function renderTokenChart(tokenUsage) {
|
|
|
1287
1365
|
});
|
|
1288
1366
|
if (maxTokens === 0) maxTokens = 1;
|
|
1289
1367
|
|
|
1290
|
-
var
|
|
1291
|
-
var
|
|
1368
|
+
var chartWidth = chart.clientWidth || 800;
|
|
1369
|
+
var minSvgWidth = buckets.length * 16 + 40;
|
|
1370
|
+
var svgWidth = Math.max(chartWidth, minSvgWidth);
|
|
1371
|
+
var availableWidth = svgWidth - 50;
|
|
1372
|
+
var step = availableWidth / Math.max(1, buckets.length);
|
|
1373
|
+
var barWidth = Math.min(36, step * 0.8);
|
|
1292
1374
|
var chartHeight = 140;
|
|
1293
|
-
var svgWidth = buckets.length * (barWidth + gap) + 60;
|
|
1294
1375
|
|
|
1295
1376
|
var bars = '';
|
|
1296
1377
|
buckets.forEach(function(b, i) {
|
|
1297
|
-
var x = 40 + i * (barWidth
|
|
1378
|
+
var x = 40 + i * step + (step - barWidth) / 2;
|
|
1298
1379
|
|
|
1299
1380
|
// Stack by model
|
|
1300
1381
|
var yOffset = chartHeight;
|
|
@@ -1365,12 +1446,14 @@ function renderHeatmap(tokenUsage) {
|
|
|
1365
1446
|
var hours = tokenUsage.hours || [];
|
|
1366
1447
|
var minutes = tokenUsage.minutes || [];
|
|
1367
1448
|
var now = new Date();
|
|
1449
|
+
var daysCount = 60;
|
|
1368
1450
|
var days = [];
|
|
1369
|
-
for (var i =
|
|
1451
|
+
for (var i = daysCount - 1; i >= 0; i--) {
|
|
1370
1452
|
var d = new Date(now);
|
|
1371
1453
|
d.setDate(now.getDate() - i);
|
|
1372
|
-
var key = d.
|
|
1373
|
-
|
|
1454
|
+
var key = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
1455
|
+
// show label only for every 7th day to avoid crowding
|
|
1456
|
+
days.push({ key: key, label: (i % 7 === 0) ? key.slice(5) : '' });
|
|
1374
1457
|
}
|
|
1375
1458
|
|
|
1376
1459
|
var cellMap = {}; // day|hour -> requests
|
|
@@ -1380,18 +1463,30 @@ function renderHeatmap(tokenUsage) {
|
|
|
1380
1463
|
cellMap[k] += reqs || 0;
|
|
1381
1464
|
}
|
|
1382
1465
|
|
|
1466
|
+
function parseLocal(periodStr) {
|
|
1467
|
+
var d;
|
|
1468
|
+
if (periodStr.length === 10) d = new Date(periodStr + 'T00:00:00Z');
|
|
1469
|
+
else if (periodStr.length === 13) d = new Date(periodStr + ':00:00Z');
|
|
1470
|
+
else if (periodStr.length === 16) d = new Date(periodStr + ':00Z');
|
|
1471
|
+
else d = new Date(periodStr);
|
|
1472
|
+
|
|
1473
|
+
if (isNaN(d.getTime())) return null;
|
|
1474
|
+
return {
|
|
1475
|
+
dayKey: d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'),
|
|
1476
|
+
hour: d.getHours()
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1383
1480
|
hours.forEach(function(b) {
|
|
1384
|
-
if (!b.period
|
|
1385
|
-
var
|
|
1386
|
-
|
|
1387
|
-
addBucket(dayKey, hour, b.requests);
|
|
1481
|
+
if (!b.period) return;
|
|
1482
|
+
var loc = parseLocal(b.period);
|
|
1483
|
+
if (loc) addBucket(loc.dayKey, loc.hour, b.requests);
|
|
1388
1484
|
});
|
|
1389
1485
|
|
|
1390
1486
|
minutes.forEach(function(b) {
|
|
1391
|
-
if (!b.period
|
|
1392
|
-
var
|
|
1393
|
-
|
|
1394
|
-
addBucket(dayKey, hour, b.requests);
|
|
1487
|
+
if (!b.period) return;
|
|
1488
|
+
var loc = parseLocal(b.period);
|
|
1489
|
+
if (loc) addBucket(loc.dayKey, loc.hour, b.requests);
|
|
1395
1490
|
});
|
|
1396
1491
|
|
|
1397
1492
|
var max = 0;
|
|
@@ -1412,17 +1507,17 @@ function renderHeatmap(tokenUsage) {
|
|
|
1412
1507
|
return 'rgba(56,189,248,0.92)';
|
|
1413
1508
|
}
|
|
1414
1509
|
|
|
1415
|
-
var html = '<div style="overflow-x:auto"><table style="border-collapse:separate;border-spacing:
|
|
1416
|
-
html += '<tr><th style="color:var(--text-dim);padding-right:6px">h</th>';
|
|
1417
|
-
days.forEach(function(d) { html += '<th style="color:var(--text-dim);font-weight:500">' + d.label + '</th>'; });
|
|
1510
|
+
var html = '<div style="overflow-x:auto"><table style="width:100%;min-width:800px;border-collapse:separate;border-spacing:2px;table-layout:fixed;font-family:JetBrains Mono,monospace;font-size:0.6rem">';
|
|
1511
|
+
html += '<tr><th style="color:var(--text-dim);padding-right:6px;width:20px">h</th>';
|
|
1512
|
+
days.forEach(function(d) { html += '<th style="color:var(--text-dim);font-weight:500;text-align:left;white-space:nowrap;overflow:visible">' + d.label + '</th>'; });
|
|
1418
1513
|
html += '</tr>';
|
|
1419
1514
|
|
|
1420
|
-
for (var hour =
|
|
1421
|
-
html += '<tr><td style="color:var(--text-dim);padding-right:6px">' + String(hour).padStart(2, '0') + '</td>';
|
|
1515
|
+
for (var hour = 0; hour < 24; hour++) {
|
|
1516
|
+
html += '<tr><td style="color:var(--text-dim);padding-right:6px;text-align:right">' + String(hour).padStart(2, '0') + '</td>';
|
|
1422
1517
|
for (var j = 0; j < days.length; j++) {
|
|
1423
1518
|
var day = days[j].key;
|
|
1424
1519
|
var val = cellMap[day + '|' + hour] || 0;
|
|
1425
|
-
html += '<td title="' + day + ' ' + String(hour).padStart(2, '0') + ':00 · ' + val + ' req" style="
|
|
1520
|
+
html += '<td title="' + day + ' ' + String(hour).padStart(2, '0') + ':00 · ' + val + ' req" style="height:14px;border-radius:2px;background:' + colorFor(val) + ';border:1px solid rgba(255,255,255,0.05)"></td>';
|
|
1426
1521
|
}
|
|
1427
1522
|
html += '</tr>';
|
|
1428
1523
|
}
|
|
@@ -1637,6 +1732,22 @@ function renderRequestLog(log) {
|
|
|
1637
1732
|
});
|
|
1638
1733
|
})();
|
|
1639
1734
|
|
|
1735
|
+
function maskEventMessage(msg) {
|
|
1736
|
+
if (!MASK_MODE) return escapeHtml(msg);
|
|
1737
|
+
var out = msg;
|
|
1738
|
+
if (window.__lastData && window.__lastData.accounts) {
|
|
1739
|
+
window.__lastData.accounts.forEach(function(a) {
|
|
1740
|
+
if (a.label && out.indexOf(a.label) !== -1) {
|
|
1741
|
+
out = out.split(a.label).join('***');
|
|
1742
|
+
}
|
|
1743
|
+
if (a.email && out.indexOf(a.email) !== -1) {
|
|
1744
|
+
out = out.split(a.email).join('***');
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
return escapeHtml(out);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1640
1751
|
function renderRecentEvents(events) {
|
|
1641
1752
|
var panel = document.getElementById('recentEventsPanel');
|
|
1642
1753
|
var allEvents = events || [];
|
|
@@ -1658,7 +1769,7 @@ function renderRecentEvents(events) {
|
|
|
1658
1769
|
return '<div class="event-item level-' + (event.level || 'info') + '">' +
|
|
1659
1770
|
'<div class="event-time">' + formatTime(event.timestamp) + '</div>' +
|
|
1660
1771
|
'<div class="event-source ' + event.source + '">' + escapeHtml(event.source) + '</div>' +
|
|
1661
|
-
'<div class="event-message">' +
|
|
1772
|
+
'<div class="event-message">' + maskEventMessage(event.message) + '</div>' +
|
|
1662
1773
|
'</div>';
|
|
1663
1774
|
}).join('');
|
|
1664
1775
|
|
package/src/rotator.ts
CHANGED
|
@@ -719,8 +719,8 @@ export class AccountRotator {
|
|
|
719
719
|
|
|
720
720
|
private consolidateTokenBuckets(now: Date): void {
|
|
721
721
|
const nowMs = now.getTime();
|
|
722
|
-
const KEEP_MINUTES_MS =
|
|
723
|
-
const KEEP_HOURS_MS =
|
|
722
|
+
const KEEP_MINUTES_MS = 12 * 3600 * 1000; // keep 12h of minutes
|
|
723
|
+
const KEEP_HOURS_MS = 60 * 86400 * 1000; // keep 60d of hours
|
|
724
724
|
const KEEP_DAYS_MS = 60 * 86400 * 1000; // keep 60d of days
|
|
725
725
|
|
|
726
726
|
// Helper: parse period string to epoch ms (approximate, enough for cutoff)
|