viberadar 0.3.208 → 0.3.210
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 +307 -52
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +350 -172
- 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,16 @@ 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 loadDataDirDraft = '';
|
|
1788
|
+
let loadResultDirDraft = '';
|
|
1789
|
+
let loadView = 'library'; // 'library' | 'editor'
|
|
1772
1790
|
|
|
1773
1791
|
function toggleObsHint(id) {
|
|
1774
1792
|
document.getElementById(id).classList.toggle('open');
|
|
@@ -1859,12 +1877,14 @@ function switchMode(nextMode) {
|
|
|
1859
1877
|
clearFeatureHash();
|
|
1860
1878
|
}
|
|
1861
1879
|
if (contextMode === 'probe') { loadProbeData(); }
|
|
1862
|
-
if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
|
|
1863
|
-
if (contextMode === 'load') {
|
|
1864
|
-
loadRefreshScripts();
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1880
|
+
if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
|
|
1881
|
+
if (contextMode === 'load') {
|
|
1882
|
+
loadRefreshScripts();
|
|
1883
|
+
loadRefreshRuns();
|
|
1884
|
+
loadFetchResults();
|
|
1885
|
+
// reset to library view when switching to load tab (unless test is running)
|
|
1886
|
+
const isRunning = loadState && loadState.status === 'running';
|
|
1887
|
+
if (!isRunning) loadView = 'library';
|
|
1868
1888
|
}
|
|
1869
1889
|
setModeRoute(contextMode);
|
|
1870
1890
|
document.getElementById('searchInput').value = searchQuery;
|
|
@@ -3145,8 +3165,9 @@ async function init() {
|
|
|
3145
3165
|
setModeRoute(contextMode, true);
|
|
3146
3166
|
document.getElementById('searchInput').value = searchQuery;
|
|
3147
3167
|
|
|
3148
|
-
document.getElementById('loading').style.display = 'none';
|
|
3149
|
-
if (contextMode === 'probe') { await loadProbeData(); }
|
|
3168
|
+
document.getElementById('loading').style.display = 'none';
|
|
3169
|
+
if (contextMode === 'probe') { await loadProbeData(); }
|
|
3170
|
+
if (contextMode === 'load') { await Promise.all([loadRefreshScripts(), loadRefreshRuns(), loadFetchResults(), checkK6()]); }
|
|
3150
3171
|
renderStats();
|
|
3151
3172
|
renderSidebar();
|
|
3152
3173
|
renderContent();
|
|
@@ -3313,21 +3334,22 @@ function renderSidebar() {
|
|
|
3313
3334
|
const statusLabel = !ls ? '—' : ls.status === 'running' ? 'Запущено' : ls.status === 'done' ? 'Завершено' : ls.status === 'stopped' ? 'Остановлено' : ls.status === 'error' ? 'Ошибка' : '—';
|
|
3314
3335
|
extra.innerHTML = `
|
|
3315
3336
|
<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
|
-
|
|
3337
|
+
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45;margin-bottom:12px">
|
|
3338
|
+
k6-сценарии для API и фич. Живые графики, история запусков, AI-анализ результатов.
|
|
3339
|
+
</div>
|
|
3340
|
+
<div style="padding:0 6px;font-size:12px">
|
|
3341
|
+
<div style="color:var(--dim);margin-bottom:4px">Статус</div>
|
|
3342
|
+
<div style="color:${statusColor};font-weight:600">${statusLabel}</div>
|
|
3343
|
+
<div style="color:var(--dim);margin-top:8px">История: <span style="color:var(--text)">${loadRuns.length}</span></div>
|
|
3344
|
+
</div>
|
|
3345
|
+
${ls && ls.status !== 'idle' ? `
|
|
3346
|
+
<div style="padding:8px 6px 0;font-size:12px;color:var(--muted)">
|
|
3347
|
+
<div>Запросов: <span style="color:var(--text)">${ls.totalRequests || 0}</span></div>
|
|
3348
|
+
<div>Ошибок: <span style="color:${(ls.totalErrors||0)>0?'var(--red)':'var(--text)'}">${ls.totalErrors || 0}</span></div>
|
|
3349
|
+
${ls.summary ? `<div>RPS: <span style="color:var(--text)">${(ls.summary.rps||0).toFixed(1)}</span></div>
|
|
3350
|
+
<div>avg: <span style="color:var(--text)">${Math.round(ls.summary.avgDuration||0)}ms</span></div>
|
|
3351
|
+
<div>p95: <span style="color:var(--text)">${Math.round(ls.summary.p95Duration||0)}ms</span></div>` : ''}
|
|
3352
|
+
</div>` : ''}
|
|
3331
3353
|
${loadK6Available === false ? `<div style="padding:8px 6px;font-size:11px;color:var(--red)">⚠ k6 не найден.<br>Установите: <code>choco install k6</code></div>` : ''}`;
|
|
3332
3354
|
return;
|
|
3333
3355
|
}
|
|
@@ -7591,19 +7613,19 @@ async function checkK6() {
|
|
|
7591
7613
|
}
|
|
7592
7614
|
}
|
|
7593
7615
|
|
|
7594
|
-
function buildDefaultScript(cfg) {
|
|
7595
|
-
const baseUrl = cfg.baseUrl || 'http://localhost:5000';
|
|
7596
|
-
const endpoints = (cfg.endpoints || ['/']).filter(Boolean);
|
|
7597
|
-
const endpointLines = endpoints.map(ep =>
|
|
7598
|
-
` const r = http.get(\`\${BASE_URL}${ep}\`);\n check(r, { 'status 2xx': (res) => res.status >= 200 && res.status < 300 });`
|
|
7599
|
-
).join('\n');
|
|
7600
|
-
return `import http from 'k6/http';
|
|
7601
|
-
import { check, sleep } from 'k6';
|
|
7602
|
-
|
|
7603
|
-
const BASE_URL = '${baseUrl}';
|
|
7604
|
-
|
|
7605
|
-
export const options = {
|
|
7606
|
-
vus: ${cfg.vus || 10},
|
|
7616
|
+
function buildDefaultScript(cfg) {
|
|
7617
|
+
const baseUrl = cfg.baseUrl || 'http://localhost:5000';
|
|
7618
|
+
const endpoints = (cfg.endpoints || ['/']).filter(Boolean);
|
|
7619
|
+
const endpointLines = endpoints.map(ep =>
|
|
7620
|
+
` const r = http.get(\`\${BASE_URL}${ep}\`);\n check(r, { 'status 2xx': (res) => res.status >= 200 && res.status < 300 });`
|
|
7621
|
+
).join('\n');
|
|
7622
|
+
return `import http from 'k6/http';
|
|
7623
|
+
import { check, sleep } from 'k6';
|
|
7624
|
+
|
|
7625
|
+
const BASE_URL = __ENV.BASE_URL || '${baseUrl}';
|
|
7626
|
+
|
|
7627
|
+
export const options = {
|
|
7628
|
+
vus: ${cfg.vus || 10},
|
|
7607
7629
|
duration: '${cfg.duration || '30s'}',
|
|
7608
7630
|
thresholds: {
|
|
7609
7631
|
http_req_duration: ['p(95)<2000'],
|
|
@@ -7625,13 +7647,13 @@ function generateScriptFromFeature(featureKey) {
|
|
|
7625
7647
|
const mods = (D.modules || []).filter(m => m.featureKeys && m.featureKeys.includes(featureKey));
|
|
7626
7648
|
const apiMods = mods.filter(m => m.type === 'service' || (m.name && /api|service|controller|handler/i.test(m.name)));
|
|
7627
7649
|
const endpoints = apiMods.slice(0, 5).map(m => '/' + m.name.replace(/\.(ts|js)$/, '').replace(/\\/g, '/'));
|
|
7628
|
-
return buildDefaultScript({
|
|
7629
|
-
baseUrl: 'http://localhost:5000',
|
|
7630
|
-
vus: 10,
|
|
7631
|
-
duration: '30s',
|
|
7632
|
-
endpoints: endpoints.length ? endpoints : ['/'],
|
|
7633
|
-
});
|
|
7634
|
-
}
|
|
7650
|
+
return buildDefaultScript({
|
|
7651
|
+
baseUrl: loadBaseUrlDraft || 'http://localhost:5000',
|
|
7652
|
+
vus: loadVusDraft || 10,
|
|
7653
|
+
duration: loadDurationDraft || '30s',
|
|
7654
|
+
endpoints: endpoints.length ? endpoints : ['/'],
|
|
7655
|
+
});
|
|
7656
|
+
}
|
|
7635
7657
|
|
|
7636
7658
|
function drawLoadCharts() {
|
|
7637
7659
|
if (!loadBuckets || loadBuckets.length === 0) return;
|
|
@@ -7641,7 +7663,7 @@ function drawLoadCharts() {
|
|
|
7641
7663
|
drawLoadChart('loadChartVus', loadBuckets, b => b.vus, 'var(--yellow)', 'VUs');
|
|
7642
7664
|
}
|
|
7643
7665
|
|
|
7644
|
-
function drawLoadChart(id, buckets, valFn, color, label) {
|
|
7666
|
+
function drawLoadChart(id, buckets, valFn, color, label) {
|
|
7645
7667
|
const canvas = document.getElementById(id);
|
|
7646
7668
|
if (!canvas) return;
|
|
7647
7669
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -7718,10 +7740,42 @@ function drawLoadChart(id, buckets, valFn, color, label) {
|
|
|
7718
7740
|
ctx.fillStyle = 'rgba(125,133,144,0.9)';
|
|
7719
7741
|
ctx.font = '10px system-ui';
|
|
7720
7742
|
ctx.textAlign = 'left';
|
|
7721
|
-
ctx.fillText(label, pad.l + 2, H - 4);
|
|
7722
|
-
}
|
|
7723
|
-
|
|
7724
|
-
function
|
|
7743
|
+
ctx.fillText(label, pad.l + 2, H - 4);
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7746
|
+
function formatLoadRunDate(ts) {
|
|
7747
|
+
if (!ts) return '—';
|
|
7748
|
+
try { return new Date(ts).toLocaleString(); } catch { return '—'; }
|
|
7749
|
+
}
|
|
7750
|
+
|
|
7751
|
+
function loadStatusLabel(status) {
|
|
7752
|
+
return status === 'running' ? 'Запущено'
|
|
7753
|
+
: status === 'done' ? 'Завершено'
|
|
7754
|
+
: status === 'stopped' ? 'Остановлено'
|
|
7755
|
+
: status === 'error' ? 'Ошибка'
|
|
7756
|
+
: '—';
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7759
|
+
function applyLoadConfigToFields(cfg) {
|
|
7760
|
+
if (!cfg) return;
|
|
7761
|
+
loadBaseUrlDraft = cfg.baseUrl || loadBaseUrlDraft;
|
|
7762
|
+
loadVusDraft = cfg.vus || loadVusDraft;
|
|
7763
|
+
loadDurationDraft = cfg.duration || loadDurationDraft;
|
|
7764
|
+
loadDataDirDraft = cfg.dataDir || loadDataDirDraft;
|
|
7765
|
+
loadResultDirDraft = cfg.resultDir || loadResultDirDraft;
|
|
7766
|
+
const baseEl = document.getElementById('loadBaseUrl');
|
|
7767
|
+
const vusEl = document.getElementById('loadVus');
|
|
7768
|
+
const durEl = document.getElementById('loadDuration');
|
|
7769
|
+
const dataEl = document.getElementById('loadDataDir');
|
|
7770
|
+
const resultEl = document.getElementById('loadResultDir');
|
|
7771
|
+
if (baseEl) baseEl.value = loadBaseUrlDraft;
|
|
7772
|
+
if (vusEl) vusEl.value = loadVusDraft;
|
|
7773
|
+
if (durEl) durEl.value = loadDurationDraft;
|
|
7774
|
+
if (dataEl) dataEl.value = loadDataDirDraft;
|
|
7775
|
+
if (resultEl) resultEl.value = loadResultDirDraft;
|
|
7776
|
+
}
|
|
7777
|
+
|
|
7778
|
+
function renderLoad(c) {
|
|
7725
7779
|
const isRunning = loadState && loadState.status === 'running';
|
|
7726
7780
|
const isDone = loadState && (loadState.status === 'done' || loadState.status === 'stopped');
|
|
7727
7781
|
const hasErr = loadState && loadState.status === 'error';
|
|
@@ -7749,17 +7803,31 @@ function renderLoad(c) {
|
|
|
7749
7803
|
const view = isRunning ? 'editor' : loadView;
|
|
7750
7804
|
|
|
7751
7805
|
// ─── LIBRARY VIEW ─────────────────────────────────────────────────────────
|
|
7752
|
-
if (view === 'library') {
|
|
7753
|
-
const cards = loadSavedScripts.map(s => `
|
|
7754
|
-
<div class="load-library-card" data-sname="${escapeHtml(s.name)}">
|
|
7755
|
-
<div class="load-library-card-name">${escapeHtml(s.name)}</div>
|
|
7756
|
-
<div class="load-library-card-meta">${escapeHtml(s.date)}</div>
|
|
7757
|
-
</div>`).join('');
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7806
|
+
if (view === 'library') {
|
|
7807
|
+
const cards = loadSavedScripts.map(s => `
|
|
7808
|
+
<div class="load-library-card" data-sname="${escapeHtml(s.name)}">
|
|
7809
|
+
<div class="load-library-card-name">${escapeHtml(s.name)}</div>
|
|
7810
|
+
<div class="load-library-card-meta">${escapeHtml(s.date)} · ${s.vus || 10} VU · ${escapeHtml(s.duration || '30s')}</div>
|
|
7811
|
+
</div>`).join('');
|
|
7812
|
+
const runRows = loadRuns.slice(0, 12).map(r => {
|
|
7813
|
+
const summary = r.summary || {};
|
|
7814
|
+
const cfg = r.config || {};
|
|
7815
|
+
const err = summary.errorPct != null ? `${Number(summary.errorPct || 0).toFixed(2)}%` : '—';
|
|
7816
|
+
return `<div class="load-run-item" data-run-id="${escapeHtml(r.runId)}">
|
|
7817
|
+
<div class="load-run-main">
|
|
7818
|
+
<div class="load-run-name">${escapeHtml(r.scriptName || 'Без названия')}</div>
|
|
7819
|
+
<div class="load-run-meta">${formatLoadRunDate(r.createdAt || r.startTime)} · ${cfg.vus || '—'} VU · ${escapeHtml(cfg.duration || '—')} · ${loadStatusLabel(r.status)}</div>
|
|
7820
|
+
</div>
|
|
7821
|
+
<div class="load-run-kpi" title="${escapeHtml(cfg.resultPath || '')}">${summary.rps != null ? Number(summary.rps || 0).toFixed(1) : '—'} RPS</div>
|
|
7822
|
+
<div class="load-run-kpi">p95 ${summary.p95Duration != null ? Math.round(summary.p95Duration || 0) + 'ms' : '—'}</div>
|
|
7823
|
+
<div class="load-run-kpi" style="color:${Number(summary.errorPct || 0) > 5 ? 'var(--red)' : 'var(--muted)'}">${err}</div>
|
|
7824
|
+
</div>`;
|
|
7825
|
+
}).join('');
|
|
7826
|
+
|
|
7827
|
+
c.innerHTML = `<div class="load-screen">
|
|
7828
|
+
<div class="load-library-header">
|
|
7829
|
+
<div>
|
|
7830
|
+
<div style="font-size:16px;font-weight:600;color:var(--fg)">Нагрузочные тесты</div>
|
|
7763
7831
|
<div style="font-size:12px;color:var(--muted);margin-top:2px">${loadK6Version ? escapeHtml(loadK6Version) : ''} · ${loadSavedScripts.length} сохранённых</div>
|
|
7764
7832
|
</div>
|
|
7765
7833
|
<button class="load-btn load-btn-run" style="font-size:13px;padding:8px 20px" onclick="loadNewTest()">+ Новый тест</button>
|
|
@@ -7772,16 +7840,24 @@ function renderLoad(c) {
|
|
|
7772
7840
|
<div style="font-size:12px;color:var(--muted);margin-bottom:16px">Создай первый тест — напиши k6 скрипт или сгенерируй через AI</div>
|
|
7773
7841
|
<button class="load-btn load-btn-run" onclick="loadNewTest()">+ Создать первый тест</button>
|
|
7774
7842
|
</div>`
|
|
7775
|
-
: `<div class="load-library-grid">${cards}</div>`
|
|
7776
|
-
}
|
|
7777
|
-
|
|
7843
|
+
: `<div class="load-library-grid">${cards}</div>`
|
|
7844
|
+
}
|
|
7845
|
+
|
|
7846
|
+
<div class="load-section" style="margin:0 20px 20px">
|
|
7847
|
+
<div class="load-section-title">История запусков</div>
|
|
7848
|
+
${runRows ? `<div class="load-run-list">${runRows}</div>` : '<div style="font-size:12px;color:var(--muted)">Запусков пока нет</div>'}
|
|
7849
|
+
</div>
|
|
7850
|
+
</div>`;
|
|
7778
7851
|
|
|
7779
7852
|
// Attach click handlers via data attribute — avoids quoting issues
|
|
7780
|
-
c.querySelectorAll('.load-library-card[data-sname]').forEach(el => {
|
|
7781
|
-
el.addEventListener('click', () => loadOpenScript(el.dataset.sname));
|
|
7782
|
-
});
|
|
7783
|
-
|
|
7784
|
-
|
|
7853
|
+
c.querySelectorAll('.load-library-card[data-sname]').forEach(el => {
|
|
7854
|
+
el.addEventListener('click', () => loadOpenScript(el.dataset.sname));
|
|
7855
|
+
});
|
|
7856
|
+
c.querySelectorAll('.load-run-item[data-run-id]').forEach(el => {
|
|
7857
|
+
el.addEventListener('click', () => loadOpenRun(el.dataset.runId));
|
|
7858
|
+
});
|
|
7859
|
+
return;
|
|
7860
|
+
}
|
|
7785
7861
|
|
|
7786
7862
|
// ─── EDITOR VIEW ──────────────────────────────────────────────────────────
|
|
7787
7863
|
const features = (D && D.features) || [];
|
|
@@ -7789,9 +7865,9 @@ function renderLoad(c) {
|
|
|
7789
7865
|
`<option value="${f.key}">${f.label || f.key}</option>`
|
|
7790
7866
|
).join('');
|
|
7791
7867
|
|
|
7792
|
-
if (!loadScriptDraft) {
|
|
7793
|
-
loadScriptDraft = buildDefaultScript({ baseUrl:
|
|
7794
|
-
}
|
|
7868
|
+
if (!loadScriptDraft) {
|
|
7869
|
+
loadScriptDraft = buildDefaultScript({ baseUrl: loadBaseUrlDraft, vus: loadVusDraft, duration: loadDurationDraft, endpoints: ['/'] });
|
|
7870
|
+
}
|
|
7795
7871
|
|
|
7796
7872
|
const statusBadge = !loadState || loadState.status === 'idle' ? '' :
|
|
7797
7873
|
loadState.status === 'running' ? '<span class="load-status-badge load-status-running">● Запущено</span>' :
|
|
@@ -7813,19 +7889,31 @@ function renderLoad(c) {
|
|
|
7813
7889
|
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}</div>
|
|
7814
7890
|
<div class="load-config-row">
|
|
7815
7891
|
<div class="load-config-field" style="flex:1;min-width:200px">
|
|
7816
|
-
<label>Base URL</label>
|
|
7817
|
-
<input id="loadBaseUrl" type="text" value="
|
|
7818
|
-
</div>
|
|
7819
|
-
<div class="load-config-field">
|
|
7820
|
-
<label>VUs</label>
|
|
7821
|
-
<input id="loadVus" type="number" value="
|
|
7822
|
-
</div>
|
|
7823
|
-
<div class="load-config-field">
|
|
7824
|
-
<label>Duration</label>
|
|
7825
|
-
<input id="loadDuration" type="text" value="
|
|
7826
|
-
</div>
|
|
7827
|
-
</div>
|
|
7828
|
-
<div class="load-config-
|
|
7892
|
+
<label>Base URL</label>
|
|
7893
|
+
<input id="loadBaseUrl" type="text" value="${escapeHtml(loadBaseUrlDraft)}" placeholder="http://localhost:5000" />
|
|
7894
|
+
</div>
|
|
7895
|
+
<div class="load-config-field">
|
|
7896
|
+
<label>VUs</label>
|
|
7897
|
+
<input id="loadVus" type="number" value="${escapeHtml(String(loadVusDraft))}" min="1" max="10000" style="width:80px" />
|
|
7898
|
+
</div>
|
|
7899
|
+
<div class="load-config-field">
|
|
7900
|
+
<label>Duration</label>
|
|
7901
|
+
<input id="loadDuration" type="text" value="${escapeHtml(loadDurationDraft)}" style="width:80px" placeholder="30s" />
|
|
7902
|
+
</div>
|
|
7903
|
+
</div>
|
|
7904
|
+
<div class="load-config-hint">VUs и Duration применяются через CLI k6 и переопределяют значения из <code>export const options</code>. Base URL доступен в скрипте как <code>__ENV.BASE_URL</code>.</div>
|
|
7905
|
+
<div class="load-config-row" style="margin-top:8px">
|
|
7906
|
+
<div class="load-config-field" style="flex:1;min-width:260px">
|
|
7907
|
+
<label>Data dir / file <span style="color:var(--dim);font-weight:400">(для <code>open('./users.csv')</code> и локальных lib)</span></label>
|
|
7908
|
+
<input id="loadDataDir" type="text" value="${escapeHtml(loadDataDirDraft)}" placeholder="load-tests/k6 или C:\\path\\to\\k6-data" />
|
|
7909
|
+
</div>
|
|
7910
|
+
<div class="load-config-field" style="flex:1;min-width:260px">
|
|
7911
|
+
<label>Results dir <span style="color:var(--dim);font-weight:400">(необязательно)</span></label>
|
|
7912
|
+
<input id="loadResultDir" type="text" value="${escapeHtml(loadResultDirDraft)}" placeholder=".viberadar/load-runs или C:\\path\\to\\results" />
|
|
7913
|
+
</div>
|
|
7914
|
+
</div>
|
|
7915
|
+
<div class="load-config-hint">Если указать Data dir, VibeRadar скопирует его содержимое рядом со скриптом перед запуском. Результаты сохраняются в <code>results/<runId></code>: summary.json, metrics.ndjson, k6.log, config.json.</div>
|
|
7916
|
+
<div class="load-config-row" style="margin-top:8px">
|
|
7829
7917
|
<div class="load-config-field" style="flex:1;min-width:300px">
|
|
7830
7918
|
<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>
|
|
7831
7919
|
<input id="loadToken" type="password" placeholder="Вставь Bearer-токен (необязательно)" style="font-family:monospace;font-size:12px" oninput="localStorage.setItem('vr_load_token', this.value)" />
|
|
@@ -7879,11 +7967,13 @@ function renderLoad(c) {
|
|
|
7879
7967
|
<div class="load-summary-grid">
|
|
7880
7968
|
${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>` : ''}
|
|
7881
7969
|
${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>` : ''}
|
|
7882
|
-
${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>` : ''}
|
|
7883
|
-
${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>` : ''}
|
|
7884
|
-
${summary.
|
|
7885
|
-
${summary.
|
|
7886
|
-
|
|
7970
|
+
${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>` : ''}
|
|
7971
|
+
${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>` : ''}
|
|
7972
|
+
${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>` : ''}
|
|
7973
|
+
${summary.totalRequests != null ? `<div class="load-kpi"><div class="load-kpi-val">${summary.totalRequests||0}</div><div class="load-kpi-lbl">Запросов</div></div>` : ''}
|
|
7974
|
+
${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>` : ''}
|
|
7975
|
+
${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>` : ''}
|
|
7976
|
+
</div>
|
|
7887
7977
|
<div class="load-btns" style="margin-top:12px">
|
|
7888
7978
|
<button class="load-btn" style="background:#1a2a3a;border-color:var(--blue);color:var(--blue)" onclick="loadAiAnalysis()">🤖 AI-анализ результатов</button>
|
|
7889
7979
|
</div>
|
|
@@ -7906,9 +7996,19 @@ function renderLoad(c) {
|
|
|
7906
7996
|
const savedToken = localStorage.getItem('vr_load_token');
|
|
7907
7997
|
if (savedToken) { const el = document.getElementById('loadToken'); if (el) el.value = savedToken; }
|
|
7908
7998
|
|
|
7909
|
-
// Sync textarea with draft
|
|
7910
|
-
const ta = document.getElementById('loadScriptEditor');
|
|
7911
|
-
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
7999
|
+
// Sync textarea with draft
|
|
8000
|
+
const ta = document.getElementById('loadScriptEditor');
|
|
8001
|
+
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
8002
|
+
const baseInput = document.getElementById('loadBaseUrl');
|
|
8003
|
+
const vusInput = document.getElementById('loadVus');
|
|
8004
|
+
const durationInput = document.getElementById('loadDuration');
|
|
8005
|
+
const dataInput = document.getElementById('loadDataDir');
|
|
8006
|
+
const resultInput = document.getElementById('loadResultDir');
|
|
8007
|
+
if (baseInput) baseInput.addEventListener('input', () => { loadBaseUrlDraft = baseInput.value || 'http://localhost:5000'; });
|
|
8008
|
+
if (vusInput) vusInput.addEventListener('input', () => { loadVusDraft = parseInt(vusInput.value || '10', 10) || 10; });
|
|
8009
|
+
if (durationInput) durationInput.addEventListener('input', () => { loadDurationDraft = durationInput.value || '30s'; });
|
|
8010
|
+
if (dataInput) dataInput.addEventListener('input', () => { loadDataDirDraft = dataInput.value || ''; });
|
|
8011
|
+
if (resultInput) resultInput.addEventListener('input', () => { loadResultDirDraft = resultInput.value || ''; });
|
|
7912
8012
|
|
|
7913
8013
|
// Feature selector auto-generates script
|
|
7914
8014
|
const featSel = document.getElementById('loadFeature');
|
|
@@ -7938,14 +8038,19 @@ function loadNewTest() {
|
|
|
7938
8038
|
renderContent();
|
|
7939
8039
|
}
|
|
7940
8040
|
|
|
7941
|
-
function loadOpenScript(name) {
|
|
7942
|
-
const s = loadSavedScripts.find(x => x.name === name);
|
|
7943
|
-
if (!s) return;
|
|
7944
|
-
loadScriptDraft = s.script;
|
|
7945
|
-
loadScriptNameDraft = s.name;
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
8041
|
+
function loadOpenScript(name) {
|
|
8042
|
+
const s = loadSavedScripts.find(x => x.name === name);
|
|
8043
|
+
if (!s) return;
|
|
8044
|
+
loadScriptDraft = s.script;
|
|
8045
|
+
loadScriptNameDraft = s.name;
|
|
8046
|
+
if (s.baseUrl) loadBaseUrlDraft = s.baseUrl;
|
|
8047
|
+
if (s.vus) loadVusDraft = s.vus;
|
|
8048
|
+
if (s.duration) loadDurationDraft = s.duration;
|
|
8049
|
+
loadDataDirDraft = s.dataDir || loadDataDirDraft;
|
|
8050
|
+
loadResultDirDraft = s.resultDir || loadResultDirDraft;
|
|
8051
|
+
loadView = 'editor';
|
|
8052
|
+
renderContent();
|
|
8053
|
+
}
|
|
7949
8054
|
|
|
7950
8055
|
function loadGenerateScript() {
|
|
7951
8056
|
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
@@ -7963,23 +8068,35 @@ function loadGenerateScript() {
|
|
|
7963
8068
|
if (ta) ta.value = script;
|
|
7964
8069
|
}
|
|
7965
8070
|
|
|
7966
|
-
async function runLoadTest() {
|
|
7967
|
-
const ta = document.getElementById('loadScriptEditor');
|
|
7968
|
-
const script = ta ? ta.value : loadScriptDraft;
|
|
7969
|
-
if (!script.trim()) { alert('Скрипт пустой — сначала сгенерируйте или напишите k6-скрипт'); return; }
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
8071
|
+
async function runLoadTest() {
|
|
8072
|
+
const ta = document.getElementById('loadScriptEditor');
|
|
8073
|
+
const script = ta ? ta.value : loadScriptDraft;
|
|
8074
|
+
if (!script.trim()) { alert('Скрипт пустой — сначала сгенерируйте или напишите k6-скрипт'); return; }
|
|
8075
|
+
const baseUrl = (document.getElementById('loadBaseUrl')?.value || loadBaseUrlDraft || 'http://localhost:5000').trim();
|
|
8076
|
+
const vus = parseInt(document.getElementById('loadVus')?.value || String(loadVusDraft || 10), 10) || 10;
|
|
8077
|
+
const duration = (document.getElementById('loadDuration')?.value || loadDurationDraft || '30s').trim();
|
|
8078
|
+
const scriptName = (document.getElementById('loadScriptName')?.value || loadScriptNameDraft || 'Новый тест').trim();
|
|
8079
|
+
const dataDir = (document.getElementById('loadDataDir')?.value || loadDataDirDraft || '').trim();
|
|
8080
|
+
const resultDir = (document.getElementById('loadResultDir')?.value || loadResultDirDraft || '').trim();
|
|
8081
|
+
loadBaseUrlDraft = baseUrl;
|
|
8082
|
+
loadVusDraft = vus;
|
|
8083
|
+
loadDurationDraft = duration;
|
|
8084
|
+
loadScriptNameDraft = scriptName;
|
|
8085
|
+
loadDataDirDraft = dataDir;
|
|
8086
|
+
loadResultDirDraft = resultDir;
|
|
8087
|
+
|
|
8088
|
+
loadLogLines = [];
|
|
8089
|
+
loadBuckets = [];
|
|
7973
8090
|
|
|
7974
8091
|
try {
|
|
7975
8092
|
const tokenVal = (document.getElementById('loadToken')?.value || localStorage.getItem('vr_load_token') || '').trim();
|
|
7976
8093
|
const envVars = tokenVal ? { TOKEN: tokenVal } : {};
|
|
7977
8094
|
|
|
7978
|
-
const r = await fetch('/api/load/run', {
|
|
7979
|
-
method: 'POST',
|
|
7980
|
-
headers: { 'Content-Type': 'application/json' },
|
|
7981
|
-
body: JSON.stringify({ script, envVars }),
|
|
7982
|
-
});
|
|
8095
|
+
const r = await fetch('/api/load/run', {
|
|
8096
|
+
method: 'POST',
|
|
8097
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8098
|
+
body: JSON.stringify({ script, vus, duration, baseUrl, scriptName, dataDir, resultDir, envVars }),
|
|
8099
|
+
});
|
|
7983
8100
|
const d = await r.json();
|
|
7984
8101
|
if (!r.ok) { alert('Ошибка запуска: ' + (d.error || r.status)); return; }
|
|
7985
8102
|
} catch (e) {
|
|
@@ -8056,15 +8173,18 @@ ${featureList || '(нет данных)'}
|
|
|
8056
8173
|
- посмотри на метод, путь, body, заголовки
|
|
8057
8174
|
- найди нужные ID, ключи, форматы данных из реального кода или конфигов
|
|
8058
8175
|
|
|
8059
|
-
**Шаг 2 — составь k6 скрипт.**
|
|
8060
|
-
Требования:
|
|
8061
|
-
1. Валидный JavaScript для k6 (import from 'k6/http', 'k6', 'k6/data')
|
|
8062
|
-
2. \`export const options
|
|
8063
|
-
3. \`export default function() { ... }\` с проверками \`check()\`
|
|
8064
|
-
4.
|
|
8065
|
-
5. Если нужна
|
|
8066
|
-
6.
|
|
8067
|
-
7.
|
|
8176
|
+
**Шаг 2 — составь k6 скрипт.**
|
|
8177
|
+
Требования:
|
|
8178
|
+
1. Валидный JavaScript для k6 (import from 'k6/http', 'k6', 'k6/data')
|
|
8179
|
+
2. VUs и duration задаёт VibeRadar через CLI, поэтому не хардкодь нагрузку как единственный источник истины; если добавляешь \`export const options\`, оставь там только thresholds/summaryTrendStats или безопасные дефолты
|
|
8180
|
+
3. \`export default function() { ... }\` с проверками \`check()\`
|
|
8181
|
+
4. Base URL бери из \`__ENV.BASE_URL || '${baseUrl}'\`
|
|
8182
|
+
5. Если нужна авторизация — добавь заголовок Authorization (Bearer-токен как переменную __ENV.TOKEN)
|
|
8183
|
+
6. Если нужна загрузка файла — используй \`http.file()\` и \`open()\`
|
|
8184
|
+
7. Если нужны CSV/JSON/локальные helper-модули, используй относительные пути от Data dir: \`open('./users.csv')\`, \`import './lib/helper.js'\`
|
|
8185
|
+
8. Не добавляй \`handleSummary\` и не пиши результаты сам: VibeRadar сохраняет summary.json, metrics.ndjson, k6.log
|
|
8186
|
+
9. Добавь \`sleep(1)\` между запросами
|
|
8187
|
+
10. Добавь комментарий в начале: что тестируется и какой эндпоинт
|
|
8068
8188
|
|
|
8069
8189
|
**Шаг 3 — сохрани ТОЛЬКО скрипт в файл.**
|
|
8070
8190
|
Запиши итоговый JavaScript-код в файл: \`.viberadar/load-script-generated.js\`
|
|
@@ -8116,7 +8236,7 @@ async function loadFetchAiScript() {
|
|
|
8116
8236
|
return false;
|
|
8117
8237
|
}
|
|
8118
8238
|
|
|
8119
|
-
async function loadRefreshScripts() {
|
|
8239
|
+
async function loadRefreshScripts() {
|
|
8120
8240
|
try {
|
|
8121
8241
|
const r = await fetch('/api/load/scripts');
|
|
8122
8242
|
if (!r.ok) return;
|
|
@@ -8124,22 +8244,71 @@ async function loadRefreshScripts() {
|
|
|
8124
8244
|
// Only re-render if we're on load tab in library view (don't disrupt editor)
|
|
8125
8245
|
if (contextMode === 'load' && loadView === 'library') renderContent();
|
|
8126
8246
|
} catch {}
|
|
8127
|
-
}
|
|
8128
|
-
|
|
8129
|
-
async function
|
|
8247
|
+
}
|
|
8248
|
+
|
|
8249
|
+
async function loadRefreshRuns() {
|
|
8250
|
+
try {
|
|
8251
|
+
const r = await fetch('/api/load/runs');
|
|
8252
|
+
if (!r.ok) return;
|
|
8253
|
+
loadRuns = await r.json();
|
|
8254
|
+
if (contextMode === 'load' && loadView === 'library') renderContent();
|
|
8255
|
+
} catch {}
|
|
8256
|
+
}
|
|
8257
|
+
|
|
8258
|
+
async function loadFetchResults() {
|
|
8259
|
+
try {
|
|
8260
|
+
const r = await fetch('/api/load/results');
|
|
8261
|
+
if (!r.ok) return;
|
|
8262
|
+
const d = await r.json();
|
|
8263
|
+
if (d && d.status && d.status !== 'idle') {
|
|
8264
|
+
loadState = d;
|
|
8265
|
+
loadBuckets = d.buckets || [];
|
|
8266
|
+
loadLogLines = d.logs || [];
|
|
8267
|
+
if (d.config) applyLoadConfigToFields(d.config);
|
|
8268
|
+
}
|
|
8269
|
+
} catch {}
|
|
8270
|
+
}
|
|
8271
|
+
|
|
8272
|
+
async function loadOpenRun(runId) {
|
|
8273
|
+
try {
|
|
8274
|
+
const r = await fetch('/api/load/runs/' + encodeURIComponent(runId));
|
|
8275
|
+
if (!r.ok) return;
|
|
8276
|
+
const d = await r.json();
|
|
8277
|
+
loadState = d;
|
|
8278
|
+
loadBuckets = d.buckets || [];
|
|
8279
|
+
loadLogLines = d.logs || [];
|
|
8280
|
+
loadScriptDraft = d.script || '';
|
|
8281
|
+
loadScriptNameDraft = d.config?.scriptName || d.scriptName || '';
|
|
8282
|
+
if (d.config) applyLoadConfigToFields(d.config);
|
|
8283
|
+
loadView = 'editor';
|
|
8284
|
+
renderContent();
|
|
8285
|
+
} catch {}
|
|
8286
|
+
}
|
|
8287
|
+
|
|
8288
|
+
async function loadSaveScript() {
|
|
8130
8289
|
const nameEl = document.getElementById('loadScriptName');
|
|
8131
8290
|
const name = (nameEl ? nameEl.value : loadScriptNameDraft).trim();
|
|
8132
8291
|
if (!name) { alert('Введи название скрипта'); nameEl && nameEl.focus(); return; }
|
|
8133
|
-
const taEl = document.getElementById('loadScriptEditor');
|
|
8134
|
-
const script = taEl ? taEl.value : loadScriptDraft;
|
|
8135
|
-
if (!script.trim()) { alert('Скрипт пустой'); return; }
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8292
|
+
const taEl = document.getElementById('loadScriptEditor');
|
|
8293
|
+
const script = taEl ? taEl.value : loadScriptDraft;
|
|
8294
|
+
if (!script.trim()) { alert('Скрипт пустой'); return; }
|
|
8295
|
+
const baseUrl = (document.getElementById('loadBaseUrl')?.value || loadBaseUrlDraft || 'http://localhost:5000').trim();
|
|
8296
|
+
const vus = parseInt(document.getElementById('loadVus')?.value || String(loadVusDraft || 10), 10) || 10;
|
|
8297
|
+
const duration = (document.getElementById('loadDuration')?.value || loadDurationDraft || '30s').trim();
|
|
8298
|
+
const dataDir = (document.getElementById('loadDataDir')?.value || loadDataDirDraft || '').trim();
|
|
8299
|
+
const resultDir = (document.getElementById('loadResultDir')?.value || loadResultDirDraft || '').trim();
|
|
8300
|
+
loadScriptNameDraft = name;
|
|
8301
|
+
loadBaseUrlDraft = baseUrl;
|
|
8302
|
+
loadVusDraft = vus;
|
|
8303
|
+
loadDurationDraft = duration;
|
|
8304
|
+
loadDataDirDraft = dataDir;
|
|
8305
|
+
loadResultDirDraft = resultDir;
|
|
8306
|
+
try {
|
|
8307
|
+
const r = await fetch('/api/load/scripts', {
|
|
8308
|
+
method: 'POST',
|
|
8309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8310
|
+
body: JSON.stringify({ name, script, baseUrl, vus, duration, dataDir, resultDir }),
|
|
8311
|
+
});
|
|
8143
8312
|
if (!r.ok) { const d = await r.json().catch(() => ({})); alert('Ошибка: ' + (d.error || r.status)); return; }
|
|
8144
8313
|
await loadRefreshScripts();
|
|
8145
8314
|
// flash save button
|
|
@@ -8148,16 +8317,22 @@ async function loadSaveScript() {
|
|
|
8148
8317
|
} catch (e) { alert('Ошибка: ' + e.message); }
|
|
8149
8318
|
}
|
|
8150
8319
|
|
|
8151
|
-
async function loadLoadScript(name) {
|
|
8320
|
+
async function loadLoadScript(name) {
|
|
8152
8321
|
const s = loadSavedScripts.find(x => x.name === name);
|
|
8153
8322
|
if (!s) return;
|
|
8154
|
-
loadScriptDraft = s.script;
|
|
8155
|
-
loadScriptNameDraft = s.name;
|
|
8156
|
-
|
|
8157
|
-
if (
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8323
|
+
loadScriptDraft = s.script;
|
|
8324
|
+
loadScriptNameDraft = s.name;
|
|
8325
|
+
if (s.baseUrl) loadBaseUrlDraft = s.baseUrl;
|
|
8326
|
+
if (s.vus) loadVusDraft = s.vus;
|
|
8327
|
+
if (s.duration) loadDurationDraft = s.duration;
|
|
8328
|
+
loadDataDirDraft = s.dataDir || loadDataDirDraft;
|
|
8329
|
+
loadResultDirDraft = s.resultDir || loadResultDirDraft;
|
|
8330
|
+
const ta = document.getElementById('loadScriptEditor');
|
|
8331
|
+
if (ta) { ta.value = s.script; ta.style.borderColor = 'var(--blue)'; setTimeout(() => { ta.style.borderColor = ''; }, 1500); }
|
|
8332
|
+
const nameEl = document.getElementById('loadScriptName');
|
|
8333
|
+
if (nameEl) nameEl.value = s.name;
|
|
8334
|
+
applyLoadConfigToFields(s);
|
|
8335
|
+
}
|
|
8161
8336
|
|
|
8162
8337
|
async function loadDeleteScript(name) {
|
|
8163
8338
|
if (!confirm(`Удалить скрипт «${name}»?`)) return;
|
|
@@ -8478,13 +8653,14 @@ function connectSSE() {
|
|
|
8478
8653
|
});
|
|
8479
8654
|
|
|
8480
8655
|
// ── Load test SSE events ────────────────────────────────────────────────────
|
|
8481
|
-
es.addEventListener('load-started', (e) => {
|
|
8482
|
-
const { config } = JSON.parse(e.data);
|
|
8483
|
-
loadState = { status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0, totalErrors: 0, logs: [], script:
|
|
8484
|
-
loadBuckets = [];
|
|
8485
|
-
loadLogLines = [];
|
|
8486
|
-
if (
|
|
8487
|
-
|
|
8656
|
+
es.addEventListener('load-started', (e) => {
|
|
8657
|
+
const { runId, config } = JSON.parse(e.data);
|
|
8658
|
+
loadState = { runId, status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0, totalErrors: 0, logs: [], script: loadScriptDraft || '', config, summary: null };
|
|
8659
|
+
loadBuckets = [];
|
|
8660
|
+
loadLogLines = [];
|
|
8661
|
+
if (config) applyLoadConfigToFields(config);
|
|
8662
|
+
if (contextMode === 'load') { renderSidebar(); renderContent(); }
|
|
8663
|
+
});
|
|
8488
8664
|
|
|
8489
8665
|
es.addEventListener('load-log', (e) => {
|
|
8490
8666
|
const { line } = JSON.parse(e.data);
|
|
@@ -8502,18 +8678,20 @@ function connectSSE() {
|
|
|
8502
8678
|
}
|
|
8503
8679
|
});
|
|
8504
8680
|
|
|
8505
|
-
es.addEventListener('load-progress', (e) => {
|
|
8506
|
-
const { buckets, total, errors } = JSON.parse(e.data);
|
|
8507
|
-
loadBuckets = buckets || [];
|
|
8508
|
-
if (loadState)
|
|
8509
|
-
if (
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
if (
|
|
8516
|
-
|
|
8681
|
+
es.addEventListener('load-progress', (e) => {
|
|
8682
|
+
const { buckets, total, errors } = JSON.parse(e.data);
|
|
8683
|
+
loadBuckets = buckets || [];
|
|
8684
|
+
if (loadState) loadState.buckets = loadBuckets;
|
|
8685
|
+
if (loadState) { loadState.totalRequests = total || 0; loadState.totalErrors = errors || 0; }
|
|
8686
|
+
if (contextMode === 'load') { drawLoadCharts(); renderSidebar(); }
|
|
8687
|
+
});
|
|
8688
|
+
|
|
8689
|
+
es.addEventListener('load-done', (e) => {
|
|
8690
|
+
const { status, summary } = JSON.parse(e.data);
|
|
8691
|
+
if (loadState) { loadState.status = status; loadState.summary = summary || null; loadState.endTime = Date.now(); }
|
|
8692
|
+
loadRefreshRuns();
|
|
8693
|
+
if (contextMode === 'load') { renderSidebar(); renderContent(); }
|
|
8694
|
+
});
|
|
8517
8695
|
|
|
8518
8696
|
es.addEventListener('probe-run-started', (e) => {
|
|
8519
8697
|
const payload = JSON.parse(e.data || '{}');
|