viberadar 0.3.207 → 0.3.209
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/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +260 -46
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +317 -173
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -1498,13 +1498,25 @@
|
|
|
1498
1498
|
padding: 14px 16px; cursor: pointer; transition: border-color 0.15s, background 0.15s;
|
|
1499
1499
|
}
|
|
1500
1500
|
.load-library-card:hover { border-color: var(--blue); background: #1a2a3a22; }
|
|
1501
|
-
.load-library-card-name { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
|
1502
|
-
.load-library-card-meta { font-size: 11px; color: var(--muted); margin-bottom: 10px; }
|
|
1503
|
-
.load-library-card-actions { display: flex; gap: 6px; }
|
|
1504
|
-
.load-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1501
|
+
.load-library-card-name { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
|
1502
|
+
.load-library-card-meta { font-size: 11px; color: var(--muted); margin-bottom: 10px; }
|
|
1503
|
+
.load-library-card-actions { display: flex; gap: 6px; }
|
|
1504
|
+
.load-run-list { display: flex; flex-direction: column; gap: 6px; }
|
|
1505
|
+
.load-run-item {
|
|
1506
|
+
display: grid; grid-template-columns: 1fr auto auto auto; gap: 10px; align-items: center;
|
|
1507
|
+
background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
|
|
1508
|
+
padding: 8px 10px; cursor: pointer; font-size: 12px;
|
|
1509
|
+
}
|
|
1510
|
+
.load-run-item:hover { border-color: var(--blue); }
|
|
1511
|
+
.load-run-main { min-width: 0; }
|
|
1512
|
+
.load-run-name { color: var(--text); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1513
|
+
.load-run-meta { color: var(--muted); font-size: 11px; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1514
|
+
.load-run-kpi { color: var(--muted); font-size: 11px; white-space: nowrap; text-align: right; }
|
|
1515
|
+
.load-config-hint { font-size: 11px; color: var(--muted); margin-top: 8px; line-height: 1.45; }
|
|
1516
|
+
.load-library-empty {
|
|
1517
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
1518
|
+
padding: 60px 20px; text-align: center; color: var(--dim);
|
|
1519
|
+
}
|
|
1508
1520
|
/* Editor topbar */
|
|
1509
1521
|
.load-editor-topbar {
|
|
1510
1522
|
display: flex; align-items: center; gap: 10px;
|
|
@@ -1765,10 +1777,14 @@ let loadK6Available = null; // null = unchecked, true/false
|
|
|
1765
1777
|
let loadK6Version = '';
|
|
1766
1778
|
let loadScriptDraft = ''; // editable k6 script
|
|
1767
1779
|
let loadAiPromptDraft = ''; // AI prompt textarea draft
|
|
1768
|
-
let loadAiGenerating = false; // waiting for agent to produce script
|
|
1769
|
-
let loadSavedScripts = []; // [{ name, date, script }]
|
|
1770
|
-
let
|
|
1771
|
-
let
|
|
1780
|
+
let loadAiGenerating = false; // waiting for agent to produce script
|
|
1781
|
+
let loadSavedScripts = []; // [{ name, date, script }]
|
|
1782
|
+
let loadRuns = []; // saved k6 run history
|
|
1783
|
+
let loadScriptNameDraft = ''; // save name input draft
|
|
1784
|
+
let loadBaseUrlDraft = 'http://localhost:5000';
|
|
1785
|
+
let loadVusDraft = 10;
|
|
1786
|
+
let loadDurationDraft = '30s';
|
|
1787
|
+
let loadView = 'library'; // 'library' | 'editor'
|
|
1772
1788
|
|
|
1773
1789
|
function toggleObsHint(id) {
|
|
1774
1790
|
document.getElementById(id).classList.toggle('open');
|
|
@@ -1859,12 +1875,14 @@ function switchMode(nextMode) {
|
|
|
1859
1875
|
clearFeatureHash();
|
|
1860
1876
|
}
|
|
1861
1877
|
if (contextMode === 'probe') { loadProbeData(); }
|
|
1862
|
-
if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
|
|
1863
|
-
if (contextMode === 'load') {
|
|
1864
|
-
loadRefreshScripts();
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1878
|
+
if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
|
|
1879
|
+
if (contextMode === 'load') {
|
|
1880
|
+
loadRefreshScripts();
|
|
1881
|
+
loadRefreshRuns();
|
|
1882
|
+
loadFetchResults();
|
|
1883
|
+
// reset to library view when switching to load tab (unless test is running)
|
|
1884
|
+
const isRunning = loadState && loadState.status === 'running';
|
|
1885
|
+
if (!isRunning) loadView = 'library';
|
|
1868
1886
|
}
|
|
1869
1887
|
setModeRoute(contextMode);
|
|
1870
1888
|
document.getElementById('searchInput').value = searchQuery;
|
|
@@ -3145,8 +3163,9 @@ async function init() {
|
|
|
3145
3163
|
setModeRoute(contextMode, true);
|
|
3146
3164
|
document.getElementById('searchInput').value = searchQuery;
|
|
3147
3165
|
|
|
3148
|
-
document.getElementById('loading').style.display = 'none';
|
|
3149
|
-
if (contextMode === 'probe') { await loadProbeData(); }
|
|
3166
|
+
document.getElementById('loading').style.display = 'none';
|
|
3167
|
+
if (contextMode === 'probe') { await loadProbeData(); }
|
|
3168
|
+
if (contextMode === 'load') { await Promise.all([loadRefreshScripts(), loadRefreshRuns(), loadFetchResults(), checkK6()]); }
|
|
3150
3169
|
renderStats();
|
|
3151
3170
|
renderSidebar();
|
|
3152
3171
|
renderContent();
|
|
@@ -3313,21 +3332,22 @@ function renderSidebar() {
|
|
|
3313
3332
|
const statusLabel = !ls ? '—' : ls.status === 'running' ? 'Запущено' : ls.status === 'done' ? 'Завершено' : ls.status === 'stopped' ? 'Остановлено' : ls.status === 'error' ? 'Ошибка' : '—';
|
|
3314
3333
|
extra.innerHTML = `
|
|
3315
3334
|
<div class="sidebar-label">Нагрузочное тестирование</div>
|
|
3316
|
-
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45;margin-bottom:12px">
|
|
3317
|
-
k6-сценарии для API и фич. Живые графики, AI-анализ результатов.
|
|
3318
|
-
</div>
|
|
3319
|
-
<div style="padding:0 6px;font-size:12px">
|
|
3320
|
-
<div style="color:var(--dim);margin-bottom:4px">Статус</div>
|
|
3321
|
-
<div style="color:${statusColor};font-weight:600">${statusLabel}</div>
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
<div
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
<div>
|
|
3330
|
-
|
|
3335
|
+
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45;margin-bottom:12px">
|
|
3336
|
+
k6-сценарии для API и фич. Живые графики, история запусков, AI-анализ результатов.
|
|
3337
|
+
</div>
|
|
3338
|
+
<div style="padding:0 6px;font-size:12px">
|
|
3339
|
+
<div style="color:var(--dim);margin-bottom:4px">Статус</div>
|
|
3340
|
+
<div style="color:${statusColor};font-weight:600">${statusLabel}</div>
|
|
3341
|
+
<div style="color:var(--dim);margin-top:8px">История: <span style="color:var(--text)">${loadRuns.length}</span></div>
|
|
3342
|
+
</div>
|
|
3343
|
+
${ls && ls.status !== 'idle' ? `
|
|
3344
|
+
<div style="padding:8px 6px 0;font-size:12px;color:var(--muted)">
|
|
3345
|
+
<div>Запросов: <span style="color:var(--text)">${ls.totalRequests || 0}</span></div>
|
|
3346
|
+
<div>Ошибок: <span style="color:${(ls.totalErrors||0)>0?'var(--red)':'var(--text)'}">${ls.totalErrors || 0}</span></div>
|
|
3347
|
+
${ls.summary ? `<div>RPS: <span style="color:var(--text)">${(ls.summary.rps||0).toFixed(1)}</span></div>
|
|
3348
|
+
<div>avg: <span style="color:var(--text)">${Math.round(ls.summary.avgDuration||0)}ms</span></div>
|
|
3349
|
+
<div>p95: <span style="color:var(--text)">${Math.round(ls.summary.p95Duration||0)}ms</span></div>` : ''}
|
|
3350
|
+
</div>` : ''}
|
|
3331
3351
|
${loadK6Available === false ? `<div style="padding:8px 6px;font-size:11px;color:var(--red)">⚠ k6 не найден.<br>Установите: <code>choco install k6</code></div>` : ''}`;
|
|
3332
3352
|
return;
|
|
3333
3353
|
}
|
|
@@ -4847,7 +4867,9 @@ function renderObsGlobalDetail(c, filterFeatureKey) {
|
|
|
4847
4867
|
const label = isUnmapped ? 'Unmapped' : (bf ? bf.label : filterFeatureKey);
|
|
4848
4868
|
const color = isUnmapped ? '#e3b341' : (bf ? bf.color : '#484f58');
|
|
4849
4869
|
const score = bf ? bf.score : null;
|
|
4850
|
-
const
|
|
4870
|
+
const featurePromptMeta = `{featureKey:'${escapeHtml(filterFeatureKey)}'}`;
|
|
4871
|
+
const featurePromptBtn = `<button class="obs-copy-btn" title="Скопировать единый prompt по наблюдаемости фичи" onclick="event.stopPropagation();obsCopyPrompt('obs-feature-review',${featurePromptMeta})">📋 Prompt по фиче</button>`;
|
|
4872
|
+
const featureRunBtn = hasAgent ? `<button class="obs-action-btn" title="Запустить единый prompt по наблюдаемости фичи в терминале VibeRadar" onclick="event.stopPropagation();runAgentTask('obs-feature-review',null,null,null,${featurePromptMeta})">🤖 Запустить</button>` : '';
|
|
4851
4873
|
return `<div class="feature-detail-header">
|
|
4852
4874
|
<button class="back-btn" onclick="setFeatureDrill(null)">← Все фичи</button>
|
|
4853
4875
|
<span class="feature-detail-name">
|
|
@@ -4856,6 +4878,7 @@ function renderObsGlobalDetail(c, filterFeatureKey) {
|
|
|
4856
4878
|
</span>
|
|
4857
4879
|
${score != null ? `<span style="font-size:20px;font-weight:700;color:${covColor(score)}">${score}%</span>` : ''}
|
|
4858
4880
|
${featurePromptBtn}
|
|
4881
|
+
${featureRunBtn}
|
|
4859
4882
|
</div>`;
|
|
4860
4883
|
})() : '';
|
|
4861
4884
|
|
|
@@ -7588,19 +7611,19 @@ async function checkK6() {
|
|
|
7588
7611
|
}
|
|
7589
7612
|
}
|
|
7590
7613
|
|
|
7591
|
-
function buildDefaultScript(cfg) {
|
|
7592
|
-
const baseUrl = cfg.baseUrl || 'http://localhost:5000';
|
|
7593
|
-
const endpoints = (cfg.endpoints || ['/']).filter(Boolean);
|
|
7594
|
-
const endpointLines = endpoints.map(ep =>
|
|
7595
|
-
` const r = http.get(\`\${BASE_URL}${ep}\`);\n check(r, { 'status 2xx': (res) => res.status >= 200 && res.status < 300 });`
|
|
7596
|
-
).join('\n');
|
|
7597
|
-
return `import http from 'k6/http';
|
|
7598
|
-
import { check, sleep } from 'k6';
|
|
7599
|
-
|
|
7600
|
-
const BASE_URL = '${baseUrl}';
|
|
7601
|
-
|
|
7602
|
-
export const options = {
|
|
7603
|
-
vus: ${cfg.vus || 10},
|
|
7614
|
+
function buildDefaultScript(cfg) {
|
|
7615
|
+
const baseUrl = cfg.baseUrl || 'http://localhost:5000';
|
|
7616
|
+
const endpoints = (cfg.endpoints || ['/']).filter(Boolean);
|
|
7617
|
+
const endpointLines = endpoints.map(ep =>
|
|
7618
|
+
` const r = http.get(\`\${BASE_URL}${ep}\`);\n check(r, { 'status 2xx': (res) => res.status >= 200 && res.status < 300 });`
|
|
7619
|
+
).join('\n');
|
|
7620
|
+
return `import http from 'k6/http';
|
|
7621
|
+
import { check, sleep } from 'k6';
|
|
7622
|
+
|
|
7623
|
+
const BASE_URL = __ENV.BASE_URL || '${baseUrl}';
|
|
7624
|
+
|
|
7625
|
+
export const options = {
|
|
7626
|
+
vus: ${cfg.vus || 10},
|
|
7604
7627
|
duration: '${cfg.duration || '30s'}',
|
|
7605
7628
|
thresholds: {
|
|
7606
7629
|
http_req_duration: ['p(95)<2000'],
|
|
@@ -7622,13 +7645,13 @@ function generateScriptFromFeature(featureKey) {
|
|
|
7622
7645
|
const mods = (D.modules || []).filter(m => m.featureKeys && m.featureKeys.includes(featureKey));
|
|
7623
7646
|
const apiMods = mods.filter(m => m.type === 'service' || (m.name && /api|service|controller|handler/i.test(m.name)));
|
|
7624
7647
|
const endpoints = apiMods.slice(0, 5).map(m => '/' + m.name.replace(/\.(ts|js)$/, '').replace(/\\/g, '/'));
|
|
7625
|
-
return buildDefaultScript({
|
|
7626
|
-
baseUrl: 'http://localhost:5000',
|
|
7627
|
-
vus: 10,
|
|
7628
|
-
duration: '30s',
|
|
7629
|
-
endpoints: endpoints.length ? endpoints : ['/'],
|
|
7630
|
-
});
|
|
7631
|
-
}
|
|
7648
|
+
return buildDefaultScript({
|
|
7649
|
+
baseUrl: loadBaseUrlDraft || 'http://localhost:5000',
|
|
7650
|
+
vus: loadVusDraft || 10,
|
|
7651
|
+
duration: loadDurationDraft || '30s',
|
|
7652
|
+
endpoints: endpoints.length ? endpoints : ['/'],
|
|
7653
|
+
});
|
|
7654
|
+
}
|
|
7632
7655
|
|
|
7633
7656
|
function drawLoadCharts() {
|
|
7634
7657
|
if (!loadBuckets || loadBuckets.length === 0) return;
|
|
@@ -7638,7 +7661,7 @@ function drawLoadCharts() {
|
|
|
7638
7661
|
drawLoadChart('loadChartVus', loadBuckets, b => b.vus, 'var(--yellow)', 'VUs');
|
|
7639
7662
|
}
|
|
7640
7663
|
|
|
7641
|
-
function drawLoadChart(id, buckets, valFn, color, label) {
|
|
7664
|
+
function drawLoadChart(id, buckets, valFn, color, label) {
|
|
7642
7665
|
const canvas = document.getElementById(id);
|
|
7643
7666
|
if (!canvas) return;
|
|
7644
7667
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -7715,10 +7738,36 @@ function drawLoadChart(id, buckets, valFn, color, label) {
|
|
|
7715
7738
|
ctx.fillStyle = 'rgba(125,133,144,0.9)';
|
|
7716
7739
|
ctx.font = '10px system-ui';
|
|
7717
7740
|
ctx.textAlign = 'left';
|
|
7718
|
-
ctx.fillText(label, pad.l + 2, H - 4);
|
|
7719
|
-
}
|
|
7720
|
-
|
|
7721
|
-
function
|
|
7741
|
+
ctx.fillText(label, pad.l + 2, H - 4);
|
|
7742
|
+
}
|
|
7743
|
+
|
|
7744
|
+
function formatLoadRunDate(ts) {
|
|
7745
|
+
if (!ts) return '—';
|
|
7746
|
+
try { return new Date(ts).toLocaleString(); } catch { return '—'; }
|
|
7747
|
+
}
|
|
7748
|
+
|
|
7749
|
+
function loadStatusLabel(status) {
|
|
7750
|
+
return status === 'running' ? 'Запущено'
|
|
7751
|
+
: status === 'done' ? 'Завершено'
|
|
7752
|
+
: status === 'stopped' ? 'Остановлено'
|
|
7753
|
+
: status === 'error' ? 'Ошибка'
|
|
7754
|
+
: '—';
|
|
7755
|
+
}
|
|
7756
|
+
|
|
7757
|
+
function applyLoadConfigToFields(cfg) {
|
|
7758
|
+
if (!cfg) return;
|
|
7759
|
+
loadBaseUrlDraft = cfg.baseUrl || loadBaseUrlDraft;
|
|
7760
|
+
loadVusDraft = cfg.vus || loadVusDraft;
|
|
7761
|
+
loadDurationDraft = cfg.duration || loadDurationDraft;
|
|
7762
|
+
const baseEl = document.getElementById('loadBaseUrl');
|
|
7763
|
+
const vusEl = document.getElementById('loadVus');
|
|
7764
|
+
const durEl = document.getElementById('loadDuration');
|
|
7765
|
+
if (baseEl) baseEl.value = loadBaseUrlDraft;
|
|
7766
|
+
if (vusEl) vusEl.value = loadVusDraft;
|
|
7767
|
+
if (durEl) durEl.value = loadDurationDraft;
|
|
7768
|
+
}
|
|
7769
|
+
|
|
7770
|
+
function renderLoad(c) {
|
|
7722
7771
|
const isRunning = loadState && loadState.status === 'running';
|
|
7723
7772
|
const isDone = loadState && (loadState.status === 'done' || loadState.status === 'stopped');
|
|
7724
7773
|
const hasErr = loadState && loadState.status === 'error';
|
|
@@ -7746,17 +7795,31 @@ function renderLoad(c) {
|
|
|
7746
7795
|
const view = isRunning ? 'editor' : loadView;
|
|
7747
7796
|
|
|
7748
7797
|
// ─── LIBRARY VIEW ─────────────────────────────────────────────────────────
|
|
7749
|
-
if (view === 'library') {
|
|
7750
|
-
const cards = loadSavedScripts.map(s => `
|
|
7751
|
-
<div class="load-library-card" data-sname="${escapeHtml(s.name)}">
|
|
7752
|
-
<div class="load-library-card-name">${escapeHtml(s.name)}</div>
|
|
7753
|
-
<div class="load-library-card-meta">${escapeHtml(s.date)}</div>
|
|
7754
|
-
</div>`).join('');
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7798
|
+
if (view === 'library') {
|
|
7799
|
+
const cards = loadSavedScripts.map(s => `
|
|
7800
|
+
<div class="load-library-card" data-sname="${escapeHtml(s.name)}">
|
|
7801
|
+
<div class="load-library-card-name">${escapeHtml(s.name)}</div>
|
|
7802
|
+
<div class="load-library-card-meta">${escapeHtml(s.date)} · ${s.vus || 10} VU · ${escapeHtml(s.duration || '30s')}</div>
|
|
7803
|
+
</div>`).join('');
|
|
7804
|
+
const runRows = loadRuns.slice(0, 12).map(r => {
|
|
7805
|
+
const summary = r.summary || {};
|
|
7806
|
+
const cfg = r.config || {};
|
|
7807
|
+
const err = summary.errorPct != null ? `${Number(summary.errorPct || 0).toFixed(2)}%` : '—';
|
|
7808
|
+
return `<div class="load-run-item" data-run-id="${escapeHtml(r.runId)}">
|
|
7809
|
+
<div class="load-run-main">
|
|
7810
|
+
<div class="load-run-name">${escapeHtml(r.scriptName || 'Без названия')}</div>
|
|
7811
|
+
<div class="load-run-meta">${formatLoadRunDate(r.createdAt || r.startTime)} · ${cfg.vus || '—'} VU · ${escapeHtml(cfg.duration || '—')} · ${loadStatusLabel(r.status)}</div>
|
|
7812
|
+
</div>
|
|
7813
|
+
<div class="load-run-kpi">${summary.rps != null ? Number(summary.rps || 0).toFixed(1) : '—'} RPS</div>
|
|
7814
|
+
<div class="load-run-kpi">p95 ${summary.p95Duration != null ? Math.round(summary.p95Duration || 0) + 'ms' : '—'}</div>
|
|
7815
|
+
<div class="load-run-kpi" style="color:${Number(summary.errorPct || 0) > 5 ? 'var(--red)' : 'var(--muted)'}">${err}</div>
|
|
7816
|
+
</div>`;
|
|
7817
|
+
}).join('');
|
|
7818
|
+
|
|
7819
|
+
c.innerHTML = `<div class="load-screen">
|
|
7820
|
+
<div class="load-library-header">
|
|
7821
|
+
<div>
|
|
7822
|
+
<div style="font-size:16px;font-weight:600;color:var(--fg)">Нагрузочные тесты</div>
|
|
7760
7823
|
<div style="font-size:12px;color:var(--muted);margin-top:2px">${loadK6Version ? escapeHtml(loadK6Version) : ''} · ${loadSavedScripts.length} сохранённых</div>
|
|
7761
7824
|
</div>
|
|
7762
7825
|
<button class="load-btn load-btn-run" style="font-size:13px;padding:8px 20px" onclick="loadNewTest()">+ Новый тест</button>
|
|
@@ -7769,16 +7832,24 @@ function renderLoad(c) {
|
|
|
7769
7832
|
<div style="font-size:12px;color:var(--muted);margin-bottom:16px">Создай первый тест — напиши k6 скрипт или сгенерируй через AI</div>
|
|
7770
7833
|
<button class="load-btn load-btn-run" onclick="loadNewTest()">+ Создать первый тест</button>
|
|
7771
7834
|
</div>`
|
|
7772
|
-
: `<div class="load-library-grid">${cards}</div>`
|
|
7773
|
-
}
|
|
7774
|
-
|
|
7835
|
+
: `<div class="load-library-grid">${cards}</div>`
|
|
7836
|
+
}
|
|
7837
|
+
|
|
7838
|
+
<div class="load-section" style="margin:0 20px 20px">
|
|
7839
|
+
<div class="load-section-title">История запусков</div>
|
|
7840
|
+
${runRows ? `<div class="load-run-list">${runRows}</div>` : '<div style="font-size:12px;color:var(--muted)">Запусков пока нет</div>'}
|
|
7841
|
+
</div>
|
|
7842
|
+
</div>`;
|
|
7775
7843
|
|
|
7776
7844
|
// Attach click handlers via data attribute — avoids quoting issues
|
|
7777
|
-
c.querySelectorAll('.load-library-card[data-sname]').forEach(el => {
|
|
7778
|
-
el.addEventListener('click', () => loadOpenScript(el.dataset.sname));
|
|
7779
|
-
});
|
|
7780
|
-
|
|
7781
|
-
|
|
7845
|
+
c.querySelectorAll('.load-library-card[data-sname]').forEach(el => {
|
|
7846
|
+
el.addEventListener('click', () => loadOpenScript(el.dataset.sname));
|
|
7847
|
+
});
|
|
7848
|
+
c.querySelectorAll('.load-run-item[data-run-id]').forEach(el => {
|
|
7849
|
+
el.addEventListener('click', () => loadOpenRun(el.dataset.runId));
|
|
7850
|
+
});
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7782
7853
|
|
|
7783
7854
|
// ─── EDITOR VIEW ──────────────────────────────────────────────────────────
|
|
7784
7855
|
const features = (D && D.features) || [];
|
|
@@ -7786,9 +7857,9 @@ function renderLoad(c) {
|
|
|
7786
7857
|
`<option value="${f.key}">${f.label || f.key}</option>`
|
|
7787
7858
|
).join('');
|
|
7788
7859
|
|
|
7789
|
-
if (!loadScriptDraft) {
|
|
7790
|
-
loadScriptDraft = buildDefaultScript({ baseUrl:
|
|
7791
|
-
}
|
|
7860
|
+
if (!loadScriptDraft) {
|
|
7861
|
+
loadScriptDraft = buildDefaultScript({ baseUrl: loadBaseUrlDraft, vus: loadVusDraft, duration: loadDurationDraft, endpoints: ['/'] });
|
|
7862
|
+
}
|
|
7792
7863
|
|
|
7793
7864
|
const statusBadge = !loadState || loadState.status === 'idle' ? '' :
|
|
7794
7865
|
loadState.status === 'running' ? '<span class="load-status-badge load-status-running">● Запущено</span>' :
|
|
@@ -7810,19 +7881,20 @@ function renderLoad(c) {
|
|
|
7810
7881
|
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}</div>
|
|
7811
7882
|
<div class="load-config-row">
|
|
7812
7883
|
<div class="load-config-field" style="flex:1;min-width:200px">
|
|
7813
|
-
<label>Base URL</label>
|
|
7814
|
-
<input id="loadBaseUrl" type="text" value="
|
|
7815
|
-
</div>
|
|
7816
|
-
<div class="load-config-field">
|
|
7817
|
-
<label>VUs</label>
|
|
7818
|
-
<input id="loadVus" type="number" value="
|
|
7819
|
-
</div>
|
|
7820
|
-
<div class="load-config-field">
|
|
7821
|
-
<label>Duration</label>
|
|
7822
|
-
<input id="loadDuration" type="text" value="
|
|
7823
|
-
</div>
|
|
7824
|
-
</div>
|
|
7825
|
-
<div class="load-config-
|
|
7884
|
+
<label>Base URL</label>
|
|
7885
|
+
<input id="loadBaseUrl" type="text" value="${escapeHtml(loadBaseUrlDraft)}" placeholder="http://localhost:5000" />
|
|
7886
|
+
</div>
|
|
7887
|
+
<div class="load-config-field">
|
|
7888
|
+
<label>VUs</label>
|
|
7889
|
+
<input id="loadVus" type="number" value="${escapeHtml(String(loadVusDraft))}" min="1" max="10000" style="width:80px" />
|
|
7890
|
+
</div>
|
|
7891
|
+
<div class="load-config-field">
|
|
7892
|
+
<label>Duration</label>
|
|
7893
|
+
<input id="loadDuration" type="text" value="${escapeHtml(loadDurationDraft)}" style="width:80px" placeholder="30s" />
|
|
7894
|
+
</div>
|
|
7895
|
+
</div>
|
|
7896
|
+
<div class="load-config-hint">VUs и Duration применяются через CLI k6 и переопределяют значения из <code>export const options</code>. Base URL доступен в скрипте как <code>__ENV.BASE_URL</code>.</div>
|
|
7897
|
+
<div class="load-config-row" style="margin-top:8px">
|
|
7826
7898
|
<div class="load-config-field" style="flex:1;min-width:300px">
|
|
7827
7899
|
<label>Bearer Token <span style="color:var(--dim);font-weight:400;font-size:11px">(→ <code style="background:var(--bg2);padding:1px 4px;border-radius:3px">__ENV.TOKEN</code>)</span></label>
|
|
7828
7900
|
<input id="loadToken" type="password" placeholder="Вставь Bearer-токен (необязательно)" style="font-family:monospace;font-size:12px" oninput="localStorage.setItem('vr_load_token', this.value)" />
|
|
@@ -7876,11 +7948,13 @@ function renderLoad(c) {
|
|
|
7876
7948
|
<div class="load-summary-grid">
|
|
7877
7949
|
${summary.rps != null ? `<div class="load-kpi"><div class="load-kpi-val" style="color:var(--blue)">${(summary.rps||0).toFixed(1)}</div><div class="load-kpi-lbl">RPS</div></div>` : ''}
|
|
7878
7950
|
${summary.avgDuration != null ? `<div class="load-kpi"><div class="load-kpi-val">${Math.round(summary.avgDuration||0)}</div><div class="load-kpi-lbl">avg ms</div></div>` : ''}
|
|
7879
|
-
${summary.p90Duration != null ? `<div class="load-kpi"><div class="load-kpi-val">${Math.round(summary.p90Duration||0)}</div><div class="load-kpi-lbl">p90 ms</div></div>` : ''}
|
|
7880
|
-
${summary.p95Duration != null ? `<div class="load-kpi"><div class="load-kpi-val">${Math.round(summary.p95Duration||0)}</div><div class="load-kpi-lbl">p95 ms</div></div>` : ''}
|
|
7881
|
-
${summary.
|
|
7882
|
-
${summary.
|
|
7883
|
-
|
|
7951
|
+
${summary.p90Duration != null ? `<div class="load-kpi"><div class="load-kpi-val">${Math.round(summary.p90Duration||0)}</div><div class="load-kpi-lbl">p90 ms</div></div>` : ''}
|
|
7952
|
+
${summary.p95Duration != null ? `<div class="load-kpi"><div class="load-kpi-val">${Math.round(summary.p95Duration||0)}</div><div class="load-kpi-lbl">p95 ms</div></div>` : ''}
|
|
7953
|
+
${summary.p99Duration != null ? `<div class="load-kpi"><div class="load-kpi-val">${Math.round(summary.p99Duration||0)}</div><div class="load-kpi-lbl">p99 ms</div></div>` : ''}
|
|
7954
|
+
${summary.totalRequests != null ? `<div class="load-kpi"><div class="load-kpi-val">${summary.totalRequests||0}</div><div class="load-kpi-lbl">Запросов</div></div>` : ''}
|
|
7955
|
+
${summary.errorPct != null ? `<div class="load-kpi"><div class="load-kpi-val" style="color:${(summary.errorPct||0)>5?'var(--red)':'var(--green)'}">${(summary.errorPct||0).toFixed(2)}%</div><div class="load-kpi-lbl">Ошибок</div></div>` : ''}
|
|
7956
|
+
${summary.checksFailed != null ? `<div class="load-kpi"><div class="load-kpi-val" style="color:${(summary.checksFailed||0)>0?'var(--red)':'var(--green)'}">${summary.checksFailed||0}</div><div class="load-kpi-lbl">check fails</div></div>` : ''}
|
|
7957
|
+
</div>
|
|
7884
7958
|
<div class="load-btns" style="margin-top:12px">
|
|
7885
7959
|
<button class="load-btn" style="background:#1a2a3a;border-color:var(--blue);color:var(--blue)" onclick="loadAiAnalysis()">🤖 AI-анализ результатов</button>
|
|
7886
7960
|
</div>
|
|
@@ -7903,9 +7977,15 @@ function renderLoad(c) {
|
|
|
7903
7977
|
const savedToken = localStorage.getItem('vr_load_token');
|
|
7904
7978
|
if (savedToken) { const el = document.getElementById('loadToken'); if (el) el.value = savedToken; }
|
|
7905
7979
|
|
|
7906
|
-
// Sync textarea with draft
|
|
7907
|
-
const ta = document.getElementById('loadScriptEditor');
|
|
7908
|
-
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
7980
|
+
// Sync textarea with draft
|
|
7981
|
+
const ta = document.getElementById('loadScriptEditor');
|
|
7982
|
+
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
7983
|
+
const baseInput = document.getElementById('loadBaseUrl');
|
|
7984
|
+
const vusInput = document.getElementById('loadVus');
|
|
7985
|
+
const durationInput = document.getElementById('loadDuration');
|
|
7986
|
+
if (baseInput) baseInput.addEventListener('input', () => { loadBaseUrlDraft = baseInput.value || 'http://localhost:5000'; });
|
|
7987
|
+
if (vusInput) vusInput.addEventListener('input', () => { loadVusDraft = parseInt(vusInput.value || '10', 10) || 10; });
|
|
7988
|
+
if (durationInput) durationInput.addEventListener('input', () => { loadDurationDraft = durationInput.value || '30s'; });
|
|
7909
7989
|
|
|
7910
7990
|
// Feature selector auto-generates script
|
|
7911
7991
|
const featSel = document.getElementById('loadFeature');
|
|
@@ -7935,14 +8015,17 @@ function loadNewTest() {
|
|
|
7935
8015
|
renderContent();
|
|
7936
8016
|
}
|
|
7937
8017
|
|
|
7938
|
-
function loadOpenScript(name) {
|
|
7939
|
-
const s = loadSavedScripts.find(x => x.name === name);
|
|
7940
|
-
if (!s) return;
|
|
7941
|
-
loadScriptDraft = s.script;
|
|
7942
|
-
loadScriptNameDraft = s.name;
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
8018
|
+
function loadOpenScript(name) {
|
|
8019
|
+
const s = loadSavedScripts.find(x => x.name === name);
|
|
8020
|
+
if (!s) return;
|
|
8021
|
+
loadScriptDraft = s.script;
|
|
8022
|
+
loadScriptNameDraft = s.name;
|
|
8023
|
+
if (s.baseUrl) loadBaseUrlDraft = s.baseUrl;
|
|
8024
|
+
if (s.vus) loadVusDraft = s.vus;
|
|
8025
|
+
if (s.duration) loadDurationDraft = s.duration;
|
|
8026
|
+
loadView = 'editor';
|
|
8027
|
+
renderContent();
|
|
8028
|
+
}
|
|
7946
8029
|
|
|
7947
8030
|
function loadGenerateScript() {
|
|
7948
8031
|
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
@@ -7960,23 +8043,31 @@ function loadGenerateScript() {
|
|
|
7960
8043
|
if (ta) ta.value = script;
|
|
7961
8044
|
}
|
|
7962
8045
|
|
|
7963
|
-
async function runLoadTest() {
|
|
7964
|
-
const ta = document.getElementById('loadScriptEditor');
|
|
7965
|
-
const script = ta ? ta.value : loadScriptDraft;
|
|
7966
|
-
if (!script.trim()) { alert('Скрипт пустой — сначала сгенерируйте или напишите k6-скрипт'); return; }
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
8046
|
+
async function runLoadTest() {
|
|
8047
|
+
const ta = document.getElementById('loadScriptEditor');
|
|
8048
|
+
const script = ta ? ta.value : loadScriptDraft;
|
|
8049
|
+
if (!script.trim()) { alert('Скрипт пустой — сначала сгенерируйте или напишите k6-скрипт'); return; }
|
|
8050
|
+
const baseUrl = (document.getElementById('loadBaseUrl')?.value || loadBaseUrlDraft || 'http://localhost:5000').trim();
|
|
8051
|
+
const vus = parseInt(document.getElementById('loadVus')?.value || String(loadVusDraft || 10), 10) || 10;
|
|
8052
|
+
const duration = (document.getElementById('loadDuration')?.value || loadDurationDraft || '30s').trim();
|
|
8053
|
+
const scriptName = (document.getElementById('loadScriptName')?.value || loadScriptNameDraft || 'Новый тест').trim();
|
|
8054
|
+
loadBaseUrlDraft = baseUrl;
|
|
8055
|
+
loadVusDraft = vus;
|
|
8056
|
+
loadDurationDraft = duration;
|
|
8057
|
+
loadScriptNameDraft = scriptName;
|
|
8058
|
+
|
|
8059
|
+
loadLogLines = [];
|
|
8060
|
+
loadBuckets = [];
|
|
7970
8061
|
|
|
7971
8062
|
try {
|
|
7972
8063
|
const tokenVal = (document.getElementById('loadToken')?.value || localStorage.getItem('vr_load_token') || '').trim();
|
|
7973
8064
|
const envVars = tokenVal ? { TOKEN: tokenVal } : {};
|
|
7974
8065
|
|
|
7975
|
-
const r = await fetch('/api/load/run', {
|
|
7976
|
-
method: 'POST',
|
|
7977
|
-
headers: { 'Content-Type': 'application/json' },
|
|
7978
|
-
body: JSON.stringify({ script, envVars }),
|
|
7979
|
-
});
|
|
8066
|
+
const r = await fetch('/api/load/run', {
|
|
8067
|
+
method: 'POST',
|
|
8068
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8069
|
+
body: JSON.stringify({ script, vus, duration, baseUrl, scriptName, envVars }),
|
|
8070
|
+
});
|
|
7980
8071
|
const d = await r.json();
|
|
7981
8072
|
if (!r.ok) { alert('Ошибка запуска: ' + (d.error || r.status)); return; }
|
|
7982
8073
|
} catch (e) {
|
|
@@ -8053,15 +8144,16 @@ ${featureList || '(нет данных)'}
|
|
|
8053
8144
|
- посмотри на метод, путь, body, заголовки
|
|
8054
8145
|
- найди нужные ID, ключи, форматы данных из реального кода или конфигов
|
|
8055
8146
|
|
|
8056
|
-
**Шаг 2 — составь k6 скрипт.**
|
|
8057
|
-
Требования:
|
|
8058
|
-
1. Валидный JavaScript для k6 (import from 'k6/http', 'k6', 'k6/data')
|
|
8059
|
-
2. \`export const options
|
|
8060
|
-
3. \`export default function() { ... }\` с проверками \`check()\`
|
|
8061
|
-
4.
|
|
8062
|
-
5. Если нужна
|
|
8063
|
-
6.
|
|
8064
|
-
7. Добавь
|
|
8147
|
+
**Шаг 2 — составь k6 скрипт.**
|
|
8148
|
+
Требования:
|
|
8149
|
+
1. Валидный JavaScript для k6 (import from 'k6/http', 'k6', 'k6/data')
|
|
8150
|
+
2. VUs и duration задаёт VibeRadar через CLI, поэтому не хардкодь нагрузку как единственный источник истины; если добавляешь \`export const options\`, оставь там только thresholds/summaryTrendStats или безопасные дефолты
|
|
8151
|
+
3. \`export default function() { ... }\` с проверками \`check()\`
|
|
8152
|
+
4. Base URL бери из \`__ENV.BASE_URL || '${baseUrl}'\`
|
|
8153
|
+
5. Если нужна авторизация — добавь заголовок Authorization (Bearer-токен как переменную __ENV.TOKEN)
|
|
8154
|
+
6. Если нужна загрузка файла — используй \`http.file()\` и \`open()\`
|
|
8155
|
+
7. Добавь \`sleep(1)\` между запросами
|
|
8156
|
+
8. Добавь комментарий в начале: что тестируется и какой эндпоинт
|
|
8065
8157
|
|
|
8066
8158
|
**Шаг 3 — сохрани ТОЛЬКО скрипт в файл.**
|
|
8067
8159
|
Запиши итоговый JavaScript-код в файл: \`.viberadar/load-script-generated.js\`
|
|
@@ -8113,7 +8205,7 @@ async function loadFetchAiScript() {
|
|
|
8113
8205
|
return false;
|
|
8114
8206
|
}
|
|
8115
8207
|
|
|
8116
|
-
async function loadRefreshScripts() {
|
|
8208
|
+
async function loadRefreshScripts() {
|
|
8117
8209
|
try {
|
|
8118
8210
|
const r = await fetch('/api/load/scripts');
|
|
8119
8211
|
if (!r.ok) return;
|
|
@@ -8121,22 +8213,67 @@ async function loadRefreshScripts() {
|
|
|
8121
8213
|
// Only re-render if we're on load tab in library view (don't disrupt editor)
|
|
8122
8214
|
if (contextMode === 'load' && loadView === 'library') renderContent();
|
|
8123
8215
|
} catch {}
|
|
8124
|
-
}
|
|
8125
|
-
|
|
8126
|
-
async function
|
|
8216
|
+
}
|
|
8217
|
+
|
|
8218
|
+
async function loadRefreshRuns() {
|
|
8219
|
+
try {
|
|
8220
|
+
const r = await fetch('/api/load/runs');
|
|
8221
|
+
if (!r.ok) return;
|
|
8222
|
+
loadRuns = await r.json();
|
|
8223
|
+
if (contextMode === 'load' && loadView === 'library') renderContent();
|
|
8224
|
+
} catch {}
|
|
8225
|
+
}
|
|
8226
|
+
|
|
8227
|
+
async function loadFetchResults() {
|
|
8228
|
+
try {
|
|
8229
|
+
const r = await fetch('/api/load/results');
|
|
8230
|
+
if (!r.ok) return;
|
|
8231
|
+
const d = await r.json();
|
|
8232
|
+
if (d && d.status && d.status !== 'idle') {
|
|
8233
|
+
loadState = d;
|
|
8234
|
+
loadBuckets = d.buckets || [];
|
|
8235
|
+
loadLogLines = d.logs || [];
|
|
8236
|
+
if (d.config) applyLoadConfigToFields(d.config);
|
|
8237
|
+
}
|
|
8238
|
+
} catch {}
|
|
8239
|
+
}
|
|
8240
|
+
|
|
8241
|
+
async function loadOpenRun(runId) {
|
|
8242
|
+
try {
|
|
8243
|
+
const r = await fetch('/api/load/runs/' + encodeURIComponent(runId));
|
|
8244
|
+
if (!r.ok) return;
|
|
8245
|
+
const d = await r.json();
|
|
8246
|
+
loadState = d;
|
|
8247
|
+
loadBuckets = d.buckets || [];
|
|
8248
|
+
loadLogLines = d.logs || [];
|
|
8249
|
+
loadScriptDraft = d.script || '';
|
|
8250
|
+
loadScriptNameDraft = d.config?.scriptName || d.scriptName || '';
|
|
8251
|
+
if (d.config) applyLoadConfigToFields(d.config);
|
|
8252
|
+
loadView = 'editor';
|
|
8253
|
+
renderContent();
|
|
8254
|
+
} catch {}
|
|
8255
|
+
}
|
|
8256
|
+
|
|
8257
|
+
async function loadSaveScript() {
|
|
8127
8258
|
const nameEl = document.getElementById('loadScriptName');
|
|
8128
8259
|
const name = (nameEl ? nameEl.value : loadScriptNameDraft).trim();
|
|
8129
8260
|
if (!name) { alert('Введи название скрипта'); nameEl && nameEl.focus(); return; }
|
|
8130
|
-
const taEl = document.getElementById('loadScriptEditor');
|
|
8131
|
-
const script = taEl ? taEl.value : loadScriptDraft;
|
|
8132
|
-
if (!script.trim()) { alert('Скрипт пустой'); return; }
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8261
|
+
const taEl = document.getElementById('loadScriptEditor');
|
|
8262
|
+
const script = taEl ? taEl.value : loadScriptDraft;
|
|
8263
|
+
if (!script.trim()) { alert('Скрипт пустой'); return; }
|
|
8264
|
+
const baseUrl = (document.getElementById('loadBaseUrl')?.value || loadBaseUrlDraft || 'http://localhost:5000').trim();
|
|
8265
|
+
const vus = parseInt(document.getElementById('loadVus')?.value || String(loadVusDraft || 10), 10) || 10;
|
|
8266
|
+
const duration = (document.getElementById('loadDuration')?.value || loadDurationDraft || '30s').trim();
|
|
8267
|
+
loadScriptNameDraft = name;
|
|
8268
|
+
loadBaseUrlDraft = baseUrl;
|
|
8269
|
+
loadVusDraft = vus;
|
|
8270
|
+
loadDurationDraft = duration;
|
|
8271
|
+
try {
|
|
8272
|
+
const r = await fetch('/api/load/scripts', {
|
|
8273
|
+
method: 'POST',
|
|
8274
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8275
|
+
body: JSON.stringify({ name, script, baseUrl, vus, duration }),
|
|
8276
|
+
});
|
|
8140
8277
|
if (!r.ok) { const d = await r.json().catch(() => ({})); alert('Ошибка: ' + (d.error || r.status)); return; }
|
|
8141
8278
|
await loadRefreshScripts();
|
|
8142
8279
|
// flash save button
|
|
@@ -8145,16 +8282,20 @@ async function loadSaveScript() {
|
|
|
8145
8282
|
} catch (e) { alert('Ошибка: ' + e.message); }
|
|
8146
8283
|
}
|
|
8147
8284
|
|
|
8148
|
-
async function loadLoadScript(name) {
|
|
8285
|
+
async function loadLoadScript(name) {
|
|
8149
8286
|
const s = loadSavedScripts.find(x => x.name === name);
|
|
8150
8287
|
if (!s) return;
|
|
8151
|
-
loadScriptDraft = s.script;
|
|
8152
|
-
loadScriptNameDraft = s.name;
|
|
8153
|
-
|
|
8154
|
-
if (
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
}
|
|
8288
|
+
loadScriptDraft = s.script;
|
|
8289
|
+
loadScriptNameDraft = s.name;
|
|
8290
|
+
if (s.baseUrl) loadBaseUrlDraft = s.baseUrl;
|
|
8291
|
+
if (s.vus) loadVusDraft = s.vus;
|
|
8292
|
+
if (s.duration) loadDurationDraft = s.duration;
|
|
8293
|
+
const ta = document.getElementById('loadScriptEditor');
|
|
8294
|
+
if (ta) { ta.value = s.script; ta.style.borderColor = 'var(--blue)'; setTimeout(() => { ta.style.borderColor = ''; }, 1500); }
|
|
8295
|
+
const nameEl = document.getElementById('loadScriptName');
|
|
8296
|
+
if (nameEl) nameEl.value = s.name;
|
|
8297
|
+
applyLoadConfigToFields(s);
|
|
8298
|
+
}
|
|
8158
8299
|
|
|
8159
8300
|
async function loadDeleteScript(name) {
|
|
8160
8301
|
if (!confirm(`Удалить скрипт «${name}»?`)) return;
|
|
@@ -8475,13 +8616,14 @@ function connectSSE() {
|
|
|
8475
8616
|
});
|
|
8476
8617
|
|
|
8477
8618
|
// ── Load test SSE events ────────────────────────────────────────────────────
|
|
8478
|
-
es.addEventListener('load-started', (e) => {
|
|
8479
|
-
const { config } = JSON.parse(e.data);
|
|
8480
|
-
loadState = { status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0, totalErrors: 0, logs: [], script:
|
|
8481
|
-
loadBuckets = [];
|
|
8482
|
-
loadLogLines = [];
|
|
8483
|
-
if (
|
|
8484
|
-
|
|
8619
|
+
es.addEventListener('load-started', (e) => {
|
|
8620
|
+
const { runId, config } = JSON.parse(e.data);
|
|
8621
|
+
loadState = { runId, status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0, totalErrors: 0, logs: [], script: loadScriptDraft || '', config, summary: null };
|
|
8622
|
+
loadBuckets = [];
|
|
8623
|
+
loadLogLines = [];
|
|
8624
|
+
if (config) applyLoadConfigToFields(config);
|
|
8625
|
+
if (contextMode === 'load') { renderSidebar(); renderContent(); }
|
|
8626
|
+
});
|
|
8485
8627
|
|
|
8486
8628
|
es.addEventListener('load-log', (e) => {
|
|
8487
8629
|
const { line } = JSON.parse(e.data);
|
|
@@ -8499,18 +8641,20 @@ function connectSSE() {
|
|
|
8499
8641
|
}
|
|
8500
8642
|
});
|
|
8501
8643
|
|
|
8502
|
-
es.addEventListener('load-progress', (e) => {
|
|
8503
|
-
const { buckets, total, errors } = JSON.parse(e.data);
|
|
8504
|
-
loadBuckets = buckets || [];
|
|
8505
|
-
if (loadState)
|
|
8506
|
-
if (
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
if (
|
|
8513
|
-
|
|
8644
|
+
es.addEventListener('load-progress', (e) => {
|
|
8645
|
+
const { buckets, total, errors } = JSON.parse(e.data);
|
|
8646
|
+
loadBuckets = buckets || [];
|
|
8647
|
+
if (loadState) loadState.buckets = loadBuckets;
|
|
8648
|
+
if (loadState) { loadState.totalRequests = total || 0; loadState.totalErrors = errors || 0; }
|
|
8649
|
+
if (contextMode === 'load') { drawLoadCharts(); renderSidebar(); }
|
|
8650
|
+
});
|
|
8651
|
+
|
|
8652
|
+
es.addEventListener('load-done', (e) => {
|
|
8653
|
+
const { status, summary } = JSON.parse(e.data);
|
|
8654
|
+
if (loadState) { loadState.status = status; loadState.summary = summary || null; loadState.endTime = Date.now(); }
|
|
8655
|
+
loadRefreshRuns();
|
|
8656
|
+
if (contextMode === 'load') { renderSidebar(); renderContent(); }
|
|
8657
|
+
});
|
|
8514
8658
|
|
|
8515
8659
|
es.addEventListener('probe-run-started', (e) => {
|
|
8516
8660
|
const payload = JSON.parse(e.data || '{}');
|