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.
@@ -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 = '30s';
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="text" value="${escapeHtml(loadDurationDraft)}" style="width:90px" placeholder="30s" />
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">Если в скрипте есть <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>
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 || '30s'; });
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 || '30s';
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 || '30s').trim();
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 prompt = `Проанализируй результаты нагрузочного тестирования k6:
8419
-
8420
- RPS: ${(summary.rps||0).toFixed(2)}
8421
- avg latency: ${Math.round(summary.avgDuration||0)}ms
8422
- p90 latency: ${Math.round(summary.p90Duration||0)}ms
8423
- p95 latency: ${Math.round(summary.p95Duration||0)}ms
8424
- Total requests: ${summary.totalRequests||0}
8425
- Error rate: ${(summary.errorPct||0).toFixed(2)}%
8426
-
8427
- Лог k6:
8428
- ${logs.slice(-50).join('\n')}
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 || '30s';
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 || '30s').trim();
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 || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viberadar",
3
- "version": "0.3.219",
3
+ "version": "0.3.221",
4
4
  "description": "Live module map with test coverage for vibecoding projects",
5
5
  "main": "./dist/cli.js",
6
6
  "bin": {