viberadar 0.3.210 → 0.3.212
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 +39 -16
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +23 -88
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -1782,8 +1782,6 @@ let loadSavedScripts = []; // [{ name, date, script }]
|
|
|
1782
1782
|
let loadRuns = []; // saved k6 run history
|
|
1783
1783
|
let loadScriptNameDraft = ''; // save name input draft
|
|
1784
1784
|
let loadBaseUrlDraft = 'http://localhost:5000';
|
|
1785
|
-
let loadVusDraft = 10;
|
|
1786
|
-
let loadDurationDraft = '30s';
|
|
1787
1785
|
let loadDataDirDraft = '';
|
|
1788
1786
|
let loadResultDirDraft = '';
|
|
1789
1787
|
let loadView = 'library'; // 'library' | 'editor'
|
|
@@ -7759,18 +7757,12 @@ function loadStatusLabel(status) {
|
|
|
7759
7757
|
function applyLoadConfigToFields(cfg) {
|
|
7760
7758
|
if (!cfg) return;
|
|
7761
7759
|
loadBaseUrlDraft = cfg.baseUrl || loadBaseUrlDraft;
|
|
7762
|
-
loadVusDraft = cfg.vus || loadVusDraft;
|
|
7763
|
-
loadDurationDraft = cfg.duration || loadDurationDraft;
|
|
7764
7760
|
loadDataDirDraft = cfg.dataDir || loadDataDirDraft;
|
|
7765
7761
|
loadResultDirDraft = cfg.resultDir || loadResultDirDraft;
|
|
7766
7762
|
const baseEl = document.getElementById('loadBaseUrl');
|
|
7767
|
-
const vusEl = document.getElementById('loadVus');
|
|
7768
|
-
const durEl = document.getElementById('loadDuration');
|
|
7769
7763
|
const dataEl = document.getElementById('loadDataDir');
|
|
7770
7764
|
const resultEl = document.getElementById('loadResultDir');
|
|
7771
7765
|
if (baseEl) baseEl.value = loadBaseUrlDraft;
|
|
7772
|
-
if (vusEl) vusEl.value = loadVusDraft;
|
|
7773
|
-
if (durEl) durEl.value = loadDurationDraft;
|
|
7774
7766
|
if (dataEl) dataEl.value = loadDataDirDraft;
|
|
7775
7767
|
if (resultEl) resultEl.value = loadResultDirDraft;
|
|
7776
7768
|
}
|
|
@@ -7807,7 +7799,7 @@ function renderLoad(c) {
|
|
|
7807
7799
|
const cards = loadSavedScripts.map(s => `
|
|
7808
7800
|
<div class="load-library-card" data-sname="${escapeHtml(s.name)}">
|
|
7809
7801
|
<div class="load-library-card-name">${escapeHtml(s.name)}</div>
|
|
7810
|
-
<div class="load-library-card-meta">${escapeHtml(s.date)}
|
|
7802
|
+
<div class="load-library-card-meta">${escapeHtml(s.date)}${s.dataDir ? ' · data: ' + escapeHtml(s.dataDir) : ''}</div>
|
|
7811
7803
|
</div>`).join('');
|
|
7812
7804
|
const runRows = loadRuns.slice(0, 12).map(r => {
|
|
7813
7805
|
const summary = r.summary || {};
|
|
@@ -7816,7 +7808,7 @@ function renderLoad(c) {
|
|
|
7816
7808
|
return `<div class="load-run-item" data-run-id="${escapeHtml(r.runId)}">
|
|
7817
7809
|
<div class="load-run-main">
|
|
7818
7810
|
<div class="load-run-name">${escapeHtml(r.scriptName || 'Без названия')}</div>
|
|
7819
|
-
<div class="load-run-meta">${formatLoadRunDate(r.createdAt || r.startTime)} · ${
|
|
7811
|
+
<div class="load-run-meta">${formatLoadRunDate(r.createdAt || r.startTime)} · ${escapeHtml(cfg.executionMode || 'auto')} · ${loadStatusLabel(r.status)}</div>
|
|
7820
7812
|
</div>
|
|
7821
7813
|
<div class="load-run-kpi" title="${escapeHtml(cfg.resultPath || '')}">${summary.rps != null ? Number(summary.rps || 0).toFixed(1) : '—'} RPS</div>
|
|
7822
7814
|
<div class="load-run-kpi">p95 ${summary.p95Duration != null ? Math.round(summary.p95Duration || 0) + 'ms' : '—'}</div>
|
|
@@ -7834,12 +7826,12 @@ function renderLoad(c) {
|
|
|
7834
7826
|
</div>
|
|
7835
7827
|
|
|
7836
7828
|
${loadSavedScripts.length === 0
|
|
7837
|
-
? `<div class="load-library-empty">
|
|
7838
|
-
<div style="font-size:32px;margin-bottom:12px">📋</div>
|
|
7839
|
-
<div style="font-size:14px;font-weight:500;margin-bottom:6px">Нет сохранённых тестов</div>
|
|
7840
|
-
<div style="font-size:12px;color:var(--muted);margin-bottom:16px"
|
|
7841
|
-
<button class="load-btn load-btn-run" onclick="loadNewTest()">+ Создать первый тест</button>
|
|
7842
|
-
</div>`
|
|
7829
|
+
? `<div class="load-library-empty">
|
|
7830
|
+
<div style="font-size:32px;margin-bottom:12px">📋</div>
|
|
7831
|
+
<div style="font-size:14px;font-weight:500;margin-bottom:6px">Нет сохранённых тестов</div>
|
|
7832
|
+
<div style="font-size:12px;color:var(--muted);margin-bottom:16px">Вставь готовый k6-скрипт и укажи папку с data-файлами при необходимости</div>
|
|
7833
|
+
<button class="load-btn load-btn-run" onclick="loadNewTest()">+ Создать первый тест</button>
|
|
7834
|
+
</div>`
|
|
7843
7835
|
: `<div class="load-library-grid">${cards}</div>`
|
|
7844
7836
|
}
|
|
7845
7837
|
|
|
@@ -7863,10 +7855,10 @@ function renderLoad(c) {
|
|
|
7863
7855
|
const features = (D && D.features) || [];
|
|
7864
7856
|
const featureOptions = features.map(f =>
|
|
7865
7857
|
`<option value="${f.key}">${f.label || f.key}</option>`
|
|
7866
|
-
).join('');
|
|
7867
|
-
|
|
7858
|
+
).join('');
|
|
7859
|
+
|
|
7868
7860
|
if (!loadScriptDraft) {
|
|
7869
|
-
loadScriptDraft = buildDefaultScript({ baseUrl: loadBaseUrlDraft,
|
|
7861
|
+
loadScriptDraft = buildDefaultScript({ baseUrl: loadBaseUrlDraft, endpoints: ['/'] });
|
|
7870
7862
|
}
|
|
7871
7863
|
|
|
7872
7864
|
const statusBadge = !loadState || loadState.status === 'idle' ? '' :
|
|
@@ -7887,21 +7879,13 @@ function renderLoad(c) {
|
|
|
7887
7879
|
|
|
7888
7880
|
<div class="load-section">
|
|
7889
7881
|
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}</div>
|
|
7890
|
-
<div class="load-config-row">
|
|
7891
|
-
<div class="load-config-field" style="flex:1;min-width:200px">
|
|
7882
|
+
<div class="load-config-row">
|
|
7883
|
+
<div class="load-config-field" style="flex:1;min-width:200px">
|
|
7892
7884
|
<label>Base URL</label>
|
|
7893
7885
|
<input id="loadBaseUrl" type="text" value="${escapeHtml(loadBaseUrlDraft)}" placeholder="http://localhost:5000" />
|
|
7894
7886
|
</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
7887
|
</div>
|
|
7904
|
-
<div class="load-config-hint"
|
|
7888
|
+
<div class="load-config-hint">Нагрузка, scenarios, thresholds и env-имена остаются в k6-скрипте. VibeRadar только передаёт <code>__ENV.BASE_URL</code>, файлы данных и сохраняет результаты.</div>
|
|
7905
7889
|
<div class="load-config-row" style="margin-top:8px">
|
|
7906
7890
|
<div class="load-config-field" style="flex:1;min-width:260px">
|
|
7907
7891
|
<label>Data dir / file <span style="color:var(--dim);font-weight:400">(для <code>open('./users.csv')</code> и локальных lib)</span></label>
|
|
@@ -7918,30 +7902,12 @@ function renderLoad(c) {
|
|
|
7918
7902
|
<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>
|
|
7919
7903
|
<input id="loadToken" type="password" placeholder="Вставь Bearer-токен (необязательно)" style="font-family:monospace;font-size:12px" oninput="localStorage.setItem('vr_load_token', this.value)" />
|
|
7920
7904
|
</div>
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
</div>` : ''}
|
|
7928
|
-
</div>
|
|
7929
|
-
<div class="load-btns">
|
|
7930
|
-
<button class="load-btn" onclick="loadGenerateScript()">⚙ Сгенерировать скрипт</button>
|
|
7931
|
-
<button class="load-btn load-btn-run" id="loadRunBtn" onclick="runLoadTest()" ${isRunning ? 'disabled' : ''}>▶ Запустить</button>
|
|
7932
|
-
<button class="load-btn load-btn-stop" id="loadStopBtn" onclick="stopLoadTest()" ${!isRunning ? 'disabled' : ''}>■ Стоп</button>
|
|
7933
|
-
</div>
|
|
7934
|
-
</div>
|
|
7935
|
-
|
|
7936
|
-
<div class="load-section">
|
|
7937
|
-
<div class="load-section-title">🤖 AI генерация скрипта</div>
|
|
7938
|
-
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Опиши сценарий — агент найдёт эндпоинты в коде и сгенерирует k6-скрипт</div>
|
|
7939
|
-
<div class="load-ai-prompt-wrap">
|
|
7940
|
-
<textarea class="load-ai-prompt" id="loadAiPrompt" placeholder="Например: загрузка аудио .mp3 на /api/transcribe с полем language=ru, нужна авторизация Bearer-токеном, тестировать 20 VU 1 минуту Или: чат с RAG — POST /api/chat с body {message, knowledge_base_id, workspace_id}, stream:false">${escapeHtml(loadAiPromptDraft)}</textarea>
|
|
7941
|
-
<button class="load-btn load-btn-ai" id="loadAiGenBtn" onclick="loadAiGenerateScript()" ${agentRunning ? 'disabled' : ''}>🤖 Сгенерировать<br>через AI</button>
|
|
7942
|
-
</div>
|
|
7943
|
-
<div class="load-ai-status" id="loadAiStatus">${loadAiGenerating ? '⏳ Агент генерирует скрипт… следи в терминале ↓' : ''}</div>
|
|
7944
|
-
</div>
|
|
7905
|
+
</div>
|
|
7906
|
+
<div class="load-btns">
|
|
7907
|
+
<button class="load-btn load-btn-run" id="loadRunBtn" onclick="runLoadTest()" ${isRunning ? 'disabled' : ''}>▶ Запустить</button>
|
|
7908
|
+
<button class="load-btn load-btn-stop" id="loadStopBtn" onclick="stopLoadTest()" ${!isRunning ? 'disabled' : ''}>■ Стоп</button>
|
|
7909
|
+
</div>
|
|
7910
|
+
</div>
|
|
7945
7911
|
|
|
7946
7912
|
<div class="load-section">
|
|
7947
7913
|
<div class="load-section-title">k6 скрипт <span style="font-weight:400;color:var(--dim)">(редактируемый)</span></div>
|
|
@@ -8000,31 +7966,12 @@ function renderLoad(c) {
|
|
|
8000
7966
|
const ta = document.getElementById('loadScriptEditor');
|
|
8001
7967
|
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
8002
7968
|
const baseInput = document.getElementById('loadBaseUrl');
|
|
8003
|
-
const vusInput = document.getElementById('loadVus');
|
|
8004
|
-
const durationInput = document.getElementById('loadDuration');
|
|
8005
7969
|
const dataInput = document.getElementById('loadDataDir');
|
|
8006
7970
|
const resultInput = document.getElementById('loadResultDir');
|
|
8007
7971
|
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
7972
|
if (dataInput) dataInput.addEventListener('input', () => { loadDataDirDraft = dataInput.value || ''; });
|
|
8011
7973
|
if (resultInput) resultInput.addEventListener('input', () => { loadResultDirDraft = resultInput.value || ''; });
|
|
8012
7974
|
|
|
8013
|
-
// Feature selector auto-generates script
|
|
8014
|
-
const featSel = document.getElementById('loadFeature');
|
|
8015
|
-
if (featSel) {
|
|
8016
|
-
featSel.addEventListener('change', () => {
|
|
8017
|
-
const fk = featSel.value;
|
|
8018
|
-
if (!fk) return;
|
|
8019
|
-
const script = generateScriptFromFeature(fk);
|
|
8020
|
-
if (script) {
|
|
8021
|
-
loadScriptDraft = script;
|
|
8022
|
-
const ta2 = document.getElementById('loadScriptEditor');
|
|
8023
|
-
if (ta2) ta2.value = script;
|
|
8024
|
-
}
|
|
8025
|
-
});
|
|
8026
|
-
}
|
|
8027
|
-
|
|
8028
7975
|
// Draw charts if we have data
|
|
8029
7976
|
if (loadBuckets && loadBuckets.length > 0) {
|
|
8030
7977
|
requestAnimationFrame(drawLoadCharts);
|
|
@@ -8044,8 +7991,6 @@ function loadOpenScript(name) {
|
|
|
8044
7991
|
loadScriptDraft = s.script;
|
|
8045
7992
|
loadScriptNameDraft = s.name;
|
|
8046
7993
|
if (s.baseUrl) loadBaseUrlDraft = s.baseUrl;
|
|
8047
|
-
if (s.vus) loadVusDraft = s.vus;
|
|
8048
|
-
if (s.duration) loadDurationDraft = s.duration;
|
|
8049
7994
|
loadDataDirDraft = s.dataDir || loadDataDirDraft;
|
|
8050
7995
|
loadResultDirDraft = s.resultDir || loadResultDirDraft;
|
|
8051
7996
|
loadView = 'editor';
|
|
@@ -8073,14 +8018,10 @@ async function runLoadTest() {
|
|
|
8073
8018
|
const script = ta ? ta.value : loadScriptDraft;
|
|
8074
8019
|
if (!script.trim()) { alert('Скрипт пустой — сначала сгенерируйте или напишите k6-скрипт'); return; }
|
|
8075
8020
|
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
8021
|
const scriptName = (document.getElementById('loadScriptName')?.value || loadScriptNameDraft || 'Новый тест').trim();
|
|
8079
8022
|
const dataDir = (document.getElementById('loadDataDir')?.value || loadDataDirDraft || '').trim();
|
|
8080
8023
|
const resultDir = (document.getElementById('loadResultDir')?.value || loadResultDirDraft || '').trim();
|
|
8081
8024
|
loadBaseUrlDraft = baseUrl;
|
|
8082
|
-
loadVusDraft = vus;
|
|
8083
|
-
loadDurationDraft = duration;
|
|
8084
8025
|
loadScriptNameDraft = scriptName;
|
|
8085
8026
|
loadDataDirDraft = dataDir;
|
|
8086
8027
|
loadResultDirDraft = resultDir;
|
|
@@ -8095,7 +8036,7 @@ async function runLoadTest() {
|
|
|
8095
8036
|
const r = await fetch('/api/load/run', {
|
|
8096
8037
|
method: 'POST',
|
|
8097
8038
|
headers: { 'Content-Type': 'application/json' },
|
|
8098
|
-
body: JSON.stringify({ script,
|
|
8039
|
+
body: JSON.stringify({ script, baseUrl, scriptName, dataDir, resultDir, envVars }),
|
|
8099
8040
|
});
|
|
8100
8041
|
const d = await r.json();
|
|
8101
8042
|
if (!r.ok) { alert('Ошибка запуска: ' + (d.error || r.status)); return; }
|
|
@@ -8176,7 +8117,7 @@ ${featureList || '(нет данных)'}
|
|
|
8176
8117
|
**Шаг 2 — составь k6 скрипт.**
|
|
8177
8118
|
Требования:
|
|
8178
8119
|
1. Валидный JavaScript для k6 (import from 'k6/http', 'k6', 'k6/data')
|
|
8179
|
-
2.
|
|
8120
|
+
2. Можно использовать \`options.scenarios\`; тогда VibeRadar в Auto-режиме не будет передавать \`--vus/--duration\`, а прокинет значения UI в \`__ENV.SMOKE_VUS\` и \`__ENV.SMOKE_DURATION\`
|
|
8180
8121
|
3. \`export default function() { ... }\` с проверками \`check()\`
|
|
8181
8122
|
4. Base URL бери из \`__ENV.BASE_URL || '${baseUrl}'\`
|
|
8182
8123
|
5. Если нужна авторизация — добавь заголовок Authorization (Bearer-токен как переменную __ENV.TOKEN)
|
|
@@ -8293,21 +8234,17 @@ async function loadSaveScript() {
|
|
|
8293
8234
|
const script = taEl ? taEl.value : loadScriptDraft;
|
|
8294
8235
|
if (!script.trim()) { alert('Скрипт пустой'); return; }
|
|
8295
8236
|
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
8237
|
const dataDir = (document.getElementById('loadDataDir')?.value || loadDataDirDraft || '').trim();
|
|
8299
8238
|
const resultDir = (document.getElementById('loadResultDir')?.value || loadResultDirDraft || '').trim();
|
|
8300
8239
|
loadScriptNameDraft = name;
|
|
8301
8240
|
loadBaseUrlDraft = baseUrl;
|
|
8302
|
-
loadVusDraft = vus;
|
|
8303
|
-
loadDurationDraft = duration;
|
|
8304
8241
|
loadDataDirDraft = dataDir;
|
|
8305
8242
|
loadResultDirDraft = resultDir;
|
|
8306
8243
|
try {
|
|
8307
8244
|
const r = await fetch('/api/load/scripts', {
|
|
8308
8245
|
method: 'POST',
|
|
8309
8246
|
headers: { 'Content-Type': 'application/json' },
|
|
8310
|
-
body: JSON.stringify({ name, script, baseUrl,
|
|
8247
|
+
body: JSON.stringify({ name, script, baseUrl, dataDir, resultDir }),
|
|
8311
8248
|
});
|
|
8312
8249
|
if (!r.ok) { const d = await r.json().catch(() => ({})); alert('Ошибка: ' + (d.error || r.status)); return; }
|
|
8313
8250
|
await loadRefreshScripts();
|
|
@@ -8323,8 +8260,6 @@ async function loadLoadScript(name) {
|
|
|
8323
8260
|
loadScriptDraft = s.script;
|
|
8324
8261
|
loadScriptNameDraft = s.name;
|
|
8325
8262
|
if (s.baseUrl) loadBaseUrlDraft = s.baseUrl;
|
|
8326
|
-
if (s.vus) loadVusDraft = s.vus;
|
|
8327
|
-
if (s.duration) loadDurationDraft = s.duration;
|
|
8328
8263
|
loadDataDirDraft = s.dataDir || loadDataDirDraft;
|
|
8329
8264
|
loadResultDirDraft = s.resultDir || loadResultDirDraft;
|
|
8330
8265
|
const ta = document.getElementById('loadScriptEditor');
|