viberadar 0.3.219 → 0.3.221
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/ui/dashboard.html +81 -37
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -1810,7 +1810,7 @@ let loadRuns = []; // saved k6 run history
|
|
|
1810
1810
|
let loadScriptNameDraft = ''; // save name input draft
|
|
1811
1811
|
let loadBaseUrlDraft = 'http://localhost:5000';
|
|
1812
1812
|
let loadVusDraft = 10;
|
|
1813
|
-
let loadDurationDraft = '
|
|
1813
|
+
let loadDurationDraft = '1m';
|
|
1814
1814
|
let loadDataDirDraft = '';
|
|
1815
1815
|
let loadResultDirDraft = '';
|
|
1816
1816
|
let loadAccountsJsonDraft = '';
|
|
@@ -7706,6 +7706,23 @@ function parseLoadDurationMs(value) {
|
|
|
7706
7706
|
return Number.isFinite(asNum) ? asNum * 1000 : 0;
|
|
7707
7707
|
}
|
|
7708
7708
|
|
|
7709
|
+
function durationToMinutesValue(value) {
|
|
7710
|
+
const text = String(value || '').trim();
|
|
7711
|
+
if (!text) return '';
|
|
7712
|
+
if (/^\d+(?:\.\d+)?$/.test(text)) return text;
|
|
7713
|
+
const ms = parseLoadDurationMs(text);
|
|
7714
|
+
if (!ms) return text;
|
|
7715
|
+
const min = ms / 60000;
|
|
7716
|
+
return Number.isInteger(min) ? String(min) : String(Math.round(min * 100) / 100);
|
|
7717
|
+
}
|
|
7718
|
+
|
|
7719
|
+
function durationInputToK6(value) {
|
|
7720
|
+
const text = String(value || '').trim();
|
|
7721
|
+
if (!text) return '1m';
|
|
7722
|
+
if (/^\d+(?:\.\d+)?$/.test(text)) return `${text}m`;
|
|
7723
|
+
return text;
|
|
7724
|
+
}
|
|
7725
|
+
|
|
7709
7726
|
function estimateLoadDurationMs(state) {
|
|
7710
7727
|
const cfg = state?.config || {};
|
|
7711
7728
|
const base = parseLoadDurationMs(cfg.duration || loadDurationDraft);
|
|
@@ -7753,6 +7770,10 @@ function updateLoadLiveStats() {
|
|
|
7753
7770
|
setText('loadAvgValue', stats.avgMs ? `${Math.round(stats.avgMs)} ms` : '—');
|
|
7754
7771
|
setText('loadErrorValue', `${stats.errorPct.toFixed(2)}%`);
|
|
7755
7772
|
setText('loadVuValue', String(Math.round(stats.vus || 0)));
|
|
7773
|
+
setText('loadChartRpsValue', stats.currentRps.toFixed(1));
|
|
7774
|
+
setText('loadChartLatencyValue', stats.avgMs ? `${Math.round(stats.avgMs)} ms` : '—');
|
|
7775
|
+
setText('loadChartErrorsValue', `${stats.errorPct.toFixed(2)}%`);
|
|
7776
|
+
setText('loadChartVusValue', String(Math.round(stats.vus || 0)));
|
|
7756
7777
|
const fill = document.getElementById('loadProgressFill');
|
|
7757
7778
|
if (fill) fill.style.width = `${stats.progressPct}%`;
|
|
7758
7779
|
}
|
|
@@ -7850,12 +7871,21 @@ function drawLoadChart(id, buckets, valFn, color, label) {
|
|
|
7850
7871
|
// Resolve CSS variable colors to actual colors
|
|
7851
7872
|
const colorMap = { 'var(--blue)': '#58a6ff', 'var(--green)': '#3fb950', 'var(--red)': '#f85149', 'var(--yellow)': '#e3b341' };
|
|
7852
7873
|
ctx.strokeStyle = colorMap[color] || color;
|
|
7853
|
-
vals.forEach((v, i) => {
|
|
7854
|
-
const x = pad.l + i * step;
|
|
7855
|
-
const y = pad.t + cH * (1 - v / maxVal);
|
|
7856
|
-
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
7857
|
-
});
|
|
7858
|
-
ctx.stroke();
|
|
7874
|
+
vals.forEach((v, i) => {
|
|
7875
|
+
const x = pad.l + i * step;
|
|
7876
|
+
const y = pad.t + cH * (1 - v / maxVal);
|
|
7877
|
+
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
7878
|
+
});
|
|
7879
|
+
ctx.stroke();
|
|
7880
|
+
|
|
7881
|
+
ctx.fillStyle = ctx.strokeStyle;
|
|
7882
|
+
vals.forEach((v, i) => {
|
|
7883
|
+
const x = pad.l + i * step;
|
|
7884
|
+
const y = pad.t + cH * (1 - v / maxVal);
|
|
7885
|
+
ctx.beginPath();
|
|
7886
|
+
ctx.arc(x, y, vals.length === 1 ? 3.5 : 2.2, 0, Math.PI * 2);
|
|
7887
|
+
ctx.fill();
|
|
7888
|
+
});
|
|
7859
7889
|
|
|
7860
7890
|
// Label
|
|
7861
7891
|
ctx.fillStyle = 'rgba(125,133,144,0.9)';
|
|
@@ -7894,7 +7924,7 @@ function applyLoadConfigToFields(cfg) {
|
|
|
7894
7924
|
const accountsEl = document.getElementById('loadAccountsJson');
|
|
7895
7925
|
if (baseEl) baseEl.value = loadBaseUrlDraft;
|
|
7896
7926
|
if (vusEl) vusEl.value = loadVusDraft;
|
|
7897
|
-
if (durEl) durEl.value = loadDurationDraft;
|
|
7927
|
+
if (durEl) durEl.value = durationToMinutesValue(loadDurationDraft);
|
|
7898
7928
|
if (dataEl) dataEl.value = loadDataDirDraft;
|
|
7899
7929
|
if (resultEl) resultEl.value = loadResultDirDraft;
|
|
7900
7930
|
if (accountsEl) accountsEl.value = loadAccountsJsonDraft;
|
|
@@ -8197,11 +8227,11 @@ function renderLoad(c) {
|
|
|
8197
8227
|
<input id="loadVus" type="number" value="${escapeHtml(String(loadVusDraft))}" min="1" max="10000" style="width:80px" />
|
|
8198
8228
|
</div>
|
|
8199
8229
|
<div class="load-config-field">
|
|
8200
|
-
<label>Duration</label>
|
|
8201
|
-
<input id="loadDuration" type="
|
|
8230
|
+
<label>Duration, min</label>
|
|
8231
|
+
<input id="loadDuration" type="number" value="${escapeHtml(durationToMinutesValue(loadDurationDraft))}" min="0.1" step="0.1" style="width:90px" placeholder="10" />
|
|
8202
8232
|
</div>
|
|
8203
8233
|
</div>
|
|
8204
|
-
<div class="load-config-hint"
|
|
8234
|
+
<div class="load-config-hint">Duration задаётся в минутах: введи <code>10</code>, VibeRadar передаст в k6 <code>10m</code>. Если в скрипте есть <code>options.scenarios</code>, VibeRadar не ломает stages через CLI, а передаёт значения как <code>__ENV.LOAD_VUS</code>/<code>__ENV.LOAD_DURATION</code> и совместимые <code>SMOKE_*</code>/<code>AUTH_*</code>. Если scenarios нет, будут использованы CLI <code>--vus</code>/<code>--duration</code>.</div>
|
|
8205
8235
|
<div class="load-config-row" style="margin-top:8px">
|
|
8206
8236
|
<div class="load-config-field" style="flex:1;min-width:260px">
|
|
8207
8237
|
<label>Data dir / file <span style="color:var(--dim);font-weight:400">(только если скрипт использует <code>open('./file')</code> или локальные imports)</span></label>
|
|
@@ -8262,10 +8292,10 @@ function renderLoad(c) {
|
|
|
8262
8292
|
</div>
|
|
8263
8293
|
</div>
|
|
8264
8294
|
<div class="load-charts" style="margin-top:12px">
|
|
8265
|
-
<div class="load-chart-box"><div class="load-chart-label"><span>Запросов/сек</span><span class="load-chart-value">${liveStats.currentRps.toFixed(1)}</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartRps"></canvas>' : chartEmpty}</div>
|
|
8266
|
-
<div class="load-chart-box"><div class="load-chart-label"><span>Среднее время ответа</span><span class="load-chart-value">${liveStats.avgMs ? `${Math.round(liveStats.avgMs)} ms` : '—'}</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartLatency"></canvas>' : chartEmpty}</div>
|
|
8267
|
-
<div class="load-chart-box"><div class="load-chart-label"><span>Ошибки</span><span class="load-chart-value">${liveStats.errorPct.toFixed(2)}%</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartErrors"></canvas>' : chartEmpty}</div>
|
|
8268
|
-
<div class="load-chart-box"><div class="load-chart-label"><span>Активные VUs</span><span class="load-chart-value">${Math.round(liveStats.vus || 0)}</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartVus"></canvas>' : chartEmpty}</div>
|
|
8295
|
+
<div class="load-chart-box"><div class="load-chart-label"><span>Запросов/сек</span><span class="load-chart-value" id="loadChartRpsValue">${liveStats.currentRps.toFixed(1)}</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartRps"></canvas>' : chartEmpty}</div>
|
|
8296
|
+
<div class="load-chart-box"><div class="load-chart-label"><span>Среднее время ответа</span><span class="load-chart-value" id="loadChartLatencyValue">${liveStats.avgMs ? `${Math.round(liveStats.avgMs)} ms` : '—'}</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartLatency"></canvas>' : chartEmpty}</div>
|
|
8297
|
+
<div class="load-chart-box"><div class="load-chart-label"><span>Ошибки</span><span class="load-chart-value" id="loadChartErrorsValue">${liveStats.errorPct.toFixed(2)}%</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartErrors"></canvas>' : chartEmpty}</div>
|
|
8298
|
+
<div class="load-chart-box"><div class="load-chart-label"><span>Активные VUs</span><span class="load-chart-value" id="loadChartVusValue">${Math.round(liveStats.vus || 0)}</span></div>${liveStats.hasBuckets ? '<canvas id="loadChartVus"></canvas>' : chartEmpty}</div>
|
|
8269
8299
|
</div>
|
|
8270
8300
|
</div>` : ''}
|
|
8271
8301
|
|
|
@@ -8313,7 +8343,7 @@ function renderLoad(c) {
|
|
|
8313
8343
|
const accountsInput = document.getElementById('loadAccountsJson');
|
|
8314
8344
|
if (baseInput) baseInput.addEventListener('input', () => { loadBaseUrlDraft = baseInput.value || 'http://localhost:5000'; });
|
|
8315
8345
|
if (vusInput) vusInput.addEventListener('input', () => { loadVusDraft = parseInt(vusInput.value || '10', 10) || 10; });
|
|
8316
|
-
if (durationInput) durationInput.addEventListener('input', () => { loadDurationDraft = durationInput.value || '
|
|
8346
|
+
if (durationInput) durationInput.addEventListener('input', () => { loadDurationDraft = durationInputToK6(durationInput.value || '1'); });
|
|
8317
8347
|
if (dataInput) dataInput.addEventListener('input', () => { loadDataDirDraft = dataInput.value || ''; });
|
|
8318
8348
|
if (resultInput) resultInput.addEventListener('input', () => { loadResultDirDraft = resultInput.value || ''; });
|
|
8319
8349
|
if (accountsInput) accountsInput.addEventListener('input', () => { loadAccountsJsonDraft = accountsInput.value || ''; });
|
|
@@ -8350,7 +8380,7 @@ function loadOpenScript(name) {
|
|
|
8350
8380
|
function loadGenerateScript() {
|
|
8351
8381
|
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
8352
8382
|
const vus = parseInt(document.getElementById('loadVus')?.value || '10');
|
|
8353
|
-
const duration = document.getElementById('loadDuration')?.value || '
|
|
8383
|
+
const duration = durationInputToK6(document.getElementById('loadDuration')?.value || '1');
|
|
8354
8384
|
const featKey = document.getElementById('loadFeature')?.value || '';
|
|
8355
8385
|
let script;
|
|
8356
8386
|
if (featKey) {
|
|
@@ -8369,7 +8399,7 @@ async function runLoadTest() {
|
|
|
8369
8399
|
if (!script.trim()) { alert('Скрипт пустой — сначала сгенерируйте или напишите k6-скрипт'); return; }
|
|
8370
8400
|
const baseUrl = (document.getElementById('loadBaseUrl')?.value || loadBaseUrlDraft || 'http://localhost:5000').trim();
|
|
8371
8401
|
const vus = parseInt(document.getElementById('loadVus')?.value || String(loadVusDraft || 10), 10) || 10;
|
|
8372
|
-
const duration = (document.getElementById('loadDuration')?.value || loadDurationDraft || '
|
|
8402
|
+
const duration = durationInputToK6(document.getElementById('loadDuration')?.value || loadDurationDraft || '1');
|
|
8373
8403
|
const scriptName = (document.getElementById('loadScriptName')?.value || loadScriptNameDraft || 'Новый тест').trim();
|
|
8374
8404
|
const dataDir = (document.getElementById('loadDataDir')?.value || loadDataDirDraft || '').trim();
|
|
8375
8405
|
const resultDir = (document.getElementById('loadResultDir')?.value || loadResultDirDraft || '').trim();
|
|
@@ -8411,23 +8441,37 @@ async function stopLoadTest() {
|
|
|
8411
8441
|
try { await fetch('/api/load/stop', { method: 'POST' }); } catch {}
|
|
8412
8442
|
}
|
|
8413
8443
|
|
|
8414
|
-
async function loadAiAnalysis() {
|
|
8415
|
-
if (!loadState || !loadState.summary) return;
|
|
8416
|
-
const summary = loadState.summary;
|
|
8417
|
-
const logs = loadState.logs || [];
|
|
8418
|
-
const
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
${
|
|
8429
|
-
|
|
8430
|
-
|
|
8444
|
+
async function loadAiAnalysis() {
|
|
8445
|
+
if (!loadState || !loadState.summary) return;
|
|
8446
|
+
const summary = loadState.summary;
|
|
8447
|
+
const logs = loadState.logs || [];
|
|
8448
|
+
const totalRequests = Number(summary.totalRequests || loadState.totalRequests || 0);
|
|
8449
|
+
if (totalRequests <= 0 && logs.length === 0) {
|
|
8450
|
+
alert('Для AI-анализа нет данных: в выбранном прогоне 0 запросов и пустой лог k6. Открой завершённый run с результатами или скопируй лог ошибки вручную.');
|
|
8451
|
+
return;
|
|
8452
|
+
}
|
|
8453
|
+
const prompt = `Проанализируй результаты нагрузочного тестирования k6:
|
|
8454
|
+
|
|
8455
|
+
Run ID: ${loadState.runId || '—'}
|
|
8456
|
+
Status: ${loadState.status || '—'}
|
|
8457
|
+
Result path: ${loadState.config?.resultPath || '—'}
|
|
8458
|
+
Script: ${loadState.config?.scriptName || '—'}
|
|
8459
|
+
VUs: ${loadState.config?.vus || '—'}
|
|
8460
|
+
Duration: ${loadState.config?.duration || '—'}
|
|
8461
|
+
|
|
8462
|
+
RPS: ${(summary.rps||0).toFixed(2)}
|
|
8463
|
+
avg latency: ${Math.round(summary.avgDuration||0)}ms
|
|
8464
|
+
p90 latency: ${Math.round(summary.p90Duration||0)}ms
|
|
8465
|
+
p95 latency: ${Math.round(summary.p95Duration||0)}ms
|
|
8466
|
+
Total requests: ${totalRequests}
|
|
8467
|
+
Error rate: ${(summary.errorPct||0).toFixed(2)}%
|
|
8468
|
+
Checks failed: ${summary.checksFailed ?? '—'}
|
|
8469
|
+
Exit code: ${summary.exitCode ?? '—'}
|
|
8470
|
+
|
|
8471
|
+
Лог k6:
|
|
8472
|
+
${logs.slice(-120).join('\n') || '(лог пустой)'}
|
|
8473
|
+
|
|
8474
|
+
Оцени: производительность, узкие места, рекомендации по оптимизации.`;
|
|
8431
8475
|
|
|
8432
8476
|
// Open agent terminal and send task
|
|
8433
8477
|
document.getElementById('agentPanel').classList.add('open');
|
|
@@ -8451,7 +8495,7 @@ async function loadAiGenerateScript() {
|
|
|
8451
8495
|
|
|
8452
8496
|
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
8453
8497
|
const vus = document.getElementById('loadVus')?.value || '10';
|
|
8454
|
-
const duration = document.getElementById('loadDuration')?.value || '
|
|
8498
|
+
const duration = durationInputToK6(document.getElementById('loadDuration')?.value || '1');
|
|
8455
8499
|
|
|
8456
8500
|
const featureList = (D && D.features || []).map(f => `- ${f.label} (key: ${f.key})`).join('\n');
|
|
8457
8501
|
|
|
@@ -8599,7 +8643,7 @@ async function loadSaveScript() {
|
|
|
8599
8643
|
if (!script.trim()) { alert('Скрипт пустой'); return; }
|
|
8600
8644
|
const baseUrl = (document.getElementById('loadBaseUrl')?.value || loadBaseUrlDraft || 'http://localhost:5000').trim();
|
|
8601
8645
|
const vus = parseInt(document.getElementById('loadVus')?.value || String(loadVusDraft || 10), 10) || 10;
|
|
8602
|
-
const duration = (document.getElementById('loadDuration')?.value || loadDurationDraft || '
|
|
8646
|
+
const duration = durationInputToK6(document.getElementById('loadDuration')?.value || loadDurationDraft || '1');
|
|
8603
8647
|
const dataDir = (document.getElementById('loadDataDir')?.value || loadDataDirDraft || '').trim();
|
|
8604
8648
|
const resultDir = (document.getElementById('loadResultDir')?.value || loadResultDirDraft || '').trim();
|
|
8605
8649
|
const accountsRaw = document.getElementById('loadAccountsJson')?.value || loadAccountsJsonDraft || '';
|