viberadar 0.3.164 → 0.3.166
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 +117 -46
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -1485,6 +1485,39 @@
|
|
|
1485
1485
|
color: var(--text); font-size: 12px; padding: 5px 9px; outline: none;
|
|
1486
1486
|
}
|
|
1487
1487
|
.load-save-input:focus { border-color: var(--blue); }
|
|
1488
|
+
/* Library view */
|
|
1489
|
+
.load-library-header {
|
|
1490
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
1491
|
+
padding: 16px 20px 12px; border-bottom: 1px solid var(--border); margin-bottom: 16px;
|
|
1492
|
+
}
|
|
1493
|
+
.load-library-grid {
|
|
1494
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
1495
|
+
gap: 12px; padding: 0 20px 20px;
|
|
1496
|
+
}
|
|
1497
|
+
.load-library-card {
|
|
1498
|
+
background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
|
|
1499
|
+
padding: 14px 16px; cursor: pointer; transition: border-color 0.15s, background 0.15s;
|
|
1500
|
+
}
|
|
1501
|
+
.load-library-card:hover { border-color: var(--blue); background: #1a2a3a22; }
|
|
1502
|
+
.load-library-card-name { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
|
|
1503
|
+
.load-library-card-meta { font-size: 11px; color: var(--muted); margin-bottom: 10px; }
|
|
1504
|
+
.load-library-card-actions { display: flex; gap: 6px; }
|
|
1505
|
+
.load-library-empty {
|
|
1506
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
1507
|
+
padding: 60px 20px; text-align: center; color: var(--dim);
|
|
1508
|
+
}
|
|
1509
|
+
/* Editor topbar */
|
|
1510
|
+
.load-editor-topbar {
|
|
1511
|
+
display: flex; align-items: center; gap: 10px;
|
|
1512
|
+
padding: 10px 20px 6px; border-bottom: 1px solid var(--border); margin-bottom: 4px;
|
|
1513
|
+
}
|
|
1514
|
+
.load-back-btn {
|
|
1515
|
+
background: none; border: 1px solid var(--border); color: var(--dim);
|
|
1516
|
+
border-radius: 5px; padding: 4px 12px; font-size: 12px; cursor: pointer;
|
|
1517
|
+
transition: border-color 0.15s, color 0.15s;
|
|
1518
|
+
}
|
|
1519
|
+
.load-back-btn:hover { border-color: var(--fg); color: var(--text); }
|
|
1520
|
+
/* Legacy list (kept for compatibility) */
|
|
1488
1521
|
.load-scripts-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
|
|
1489
1522
|
.load-script-item {
|
|
1490
1523
|
display: flex; align-items: center; gap: 8px; padding: 8px 10px;
|
|
@@ -1664,6 +1697,7 @@ let loadAiPromptDraft = ''; // AI prompt textarea draft
|
|
|
1664
1697
|
let loadAiGenerating = false; // waiting for agent to produce script
|
|
1665
1698
|
let loadSavedScripts = []; // [{ name, date, script }]
|
|
1666
1699
|
let loadScriptNameDraft = ''; // save name input draft
|
|
1700
|
+
let loadView = 'library'; // 'library' | 'editor'
|
|
1667
1701
|
|
|
1668
1702
|
function toggleObsHint(id) {
|
|
1669
1703
|
document.getElementById(id).classList.toggle('open');
|
|
@@ -1753,11 +1787,9 @@ function switchMode(nextMode) {
|
|
|
1753
1787
|
if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
|
|
1754
1788
|
if (contextMode === 'load') {
|
|
1755
1789
|
loadRefreshScripts();
|
|
1756
|
-
//
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
if (saved) { const el = document.getElementById('loadToken'); if (el) el.value = saved; }
|
|
1760
|
-
});
|
|
1790
|
+
// reset to library view when switching to load tab (unless test is running)
|
|
1791
|
+
const isRunning = loadState && loadState.status === 'running';
|
|
1792
|
+
if (!isRunning) loadView = 'library';
|
|
1761
1793
|
}
|
|
1762
1794
|
setModeRoute(contextMode);
|
|
1763
1795
|
document.getElementById('searchInput').value = searchQuery;
|
|
@@ -6766,7 +6798,7 @@ async function checkK6() {
|
|
|
6766
6798
|
}
|
|
6767
6799
|
|
|
6768
6800
|
function buildDefaultScript(cfg) {
|
|
6769
|
-
const baseUrl = cfg.baseUrl || 'http://localhost:
|
|
6801
|
+
const baseUrl = cfg.baseUrl || 'http://localhost:5000';
|
|
6770
6802
|
const endpoints = (cfg.endpoints || ['/']).filter(Boolean);
|
|
6771
6803
|
const endpointLines = endpoints.map(ep =>
|
|
6772
6804
|
` const r = http.get(\`\${BASE_URL}${ep}\`);\n check(r, { 'status 2xx': (res) => res.status >= 200 && res.status < 300 });`
|
|
@@ -6800,7 +6832,7 @@ function generateScriptFromFeature(featureKey) {
|
|
|
6800
6832
|
const apiMods = mods.filter(m => m.type === 'service' || (m.name && /api|service|controller|handler/i.test(m.name)));
|
|
6801
6833
|
const endpoints = apiMods.slice(0, 5).map(m => '/' + m.name.replace(/\.(ts|js)$/, '').replace(/\\/g, '/'));
|
|
6802
6834
|
return buildDefaultScript({
|
|
6803
|
-
baseUrl: 'http://localhost:
|
|
6835
|
+
baseUrl: 'http://localhost:5000',
|
|
6804
6836
|
vus: 10,
|
|
6805
6837
|
duration: '30s',
|
|
6806
6838
|
endpoints: endpoints.length ? endpoints : ['/'],
|
|
@@ -6900,7 +6932,7 @@ function renderLoad(c) {
|
|
|
6900
6932
|
const isDone = loadState && (loadState.status === 'done' || loadState.status === 'stopped');
|
|
6901
6933
|
const hasErr = loadState && loadState.status === 'error';
|
|
6902
6934
|
|
|
6903
|
-
// k6
|
|
6935
|
+
// k6 not installed
|
|
6904
6936
|
if (loadK6Available === false) {
|
|
6905
6937
|
c.innerHTML = `<div class="load-screen"><div class="load-no-k6">
|
|
6906
6938
|
<h3>⚠ k6 не найден</h3>
|
|
@@ -6914,20 +6946,57 @@ function renderLoad(c) {
|
|
|
6914
6946
|
</div></div>`;
|
|
6915
6947
|
return;
|
|
6916
6948
|
}
|
|
6917
|
-
|
|
6918
6949
|
if (loadK6Available === null) {
|
|
6919
6950
|
c.innerHTML = `<div class="load-screen" style="padding:40px;text-align:center;color:var(--muted)">Проверяю k6…</div>`;
|
|
6920
6951
|
return;
|
|
6921
6952
|
}
|
|
6922
6953
|
|
|
6954
|
+
// If test is running — force editor view
|
|
6955
|
+
const view = isRunning ? 'editor' : loadView;
|
|
6956
|
+
|
|
6957
|
+
// ─── LIBRARY VIEW ─────────────────────────────────────────────────────────
|
|
6958
|
+
if (view === 'library') {
|
|
6959
|
+
const cards = loadSavedScripts.map(s => `
|
|
6960
|
+
<div class="load-library-card" data-sname="${escapeHtml(s.name)}">
|
|
6961
|
+
<div class="load-library-card-name">${escapeHtml(s.name)}</div>
|
|
6962
|
+
<div class="load-library-card-meta">${escapeHtml(s.date)}</div>
|
|
6963
|
+
</div>`).join('');
|
|
6964
|
+
|
|
6965
|
+
c.innerHTML = `<div class="load-screen">
|
|
6966
|
+
<div class="load-library-header">
|
|
6967
|
+
<div>
|
|
6968
|
+
<div style="font-size:16px;font-weight:600;color:var(--fg)">Нагрузочные тесты</div>
|
|
6969
|
+
<div style="font-size:12px;color:var(--muted);margin-top:2px">${loadK6Version ? escapeHtml(loadK6Version) : ''} · ${loadSavedScripts.length} сохранённых</div>
|
|
6970
|
+
</div>
|
|
6971
|
+
<button class="load-btn load-btn-run" style="font-size:13px;padding:8px 20px" onclick="loadNewTest()">+ Новый тест</button>
|
|
6972
|
+
</div>
|
|
6973
|
+
|
|
6974
|
+
${loadSavedScripts.length === 0
|
|
6975
|
+
? `<div class="load-library-empty">
|
|
6976
|
+
<div style="font-size:32px;margin-bottom:12px">📋</div>
|
|
6977
|
+
<div style="font-size:14px;font-weight:500;margin-bottom:6px">Нет сохранённых тестов</div>
|
|
6978
|
+
<div style="font-size:12px;color:var(--muted);margin-bottom:16px">Создай первый тест — напиши k6 скрипт или сгенерируй через AI</div>
|
|
6979
|
+
<button class="load-btn load-btn-run" onclick="loadNewTest()">+ Создать первый тест</button>
|
|
6980
|
+
</div>`
|
|
6981
|
+
: `<div class="load-library-grid">${cards}</div>`
|
|
6982
|
+
}
|
|
6983
|
+
</div>`;
|
|
6984
|
+
|
|
6985
|
+
// Attach click handlers via data attribute — avoids quoting issues
|
|
6986
|
+
c.querySelectorAll('.load-library-card[data-sname]').forEach(el => {
|
|
6987
|
+
el.addEventListener('click', () => loadOpenScript(el.dataset.sname));
|
|
6988
|
+
});
|
|
6989
|
+
return;
|
|
6990
|
+
}
|
|
6991
|
+
|
|
6992
|
+
// ─── EDITOR VIEW ──────────────────────────────────────────────────────────
|
|
6923
6993
|
const features = (D && D.features) || [];
|
|
6924
6994
|
const featureOptions = features.map(f =>
|
|
6925
6995
|
`<option value="${f.key}">${f.label || f.key}</option>`
|
|
6926
6996
|
).join('');
|
|
6927
6997
|
|
|
6928
|
-
// Build script draft if empty
|
|
6929
6998
|
if (!loadScriptDraft) {
|
|
6930
|
-
loadScriptDraft = buildDefaultScript({ baseUrl: 'http://localhost:
|
|
6999
|
+
loadScriptDraft = buildDefaultScript({ baseUrl: 'http://localhost:5000', vus: 10, duration: '30s', endpoints: ['/'] });
|
|
6931
7000
|
}
|
|
6932
7001
|
|
|
6933
7002
|
const statusBadge = !loadState || loadState.status === 'idle' ? '' :
|
|
@@ -6941,12 +7010,17 @@ function renderLoad(c) {
|
|
|
6941
7010
|
|
|
6942
7011
|
c.innerHTML = `<div class="load-screen">
|
|
6943
7012
|
|
|
7013
|
+
<div class="load-editor-topbar">
|
|
7014
|
+
${!isRunning ? `<button class="load-back-btn" onclick="loadView='library';renderContent()">← Все тесты</button>` : ''}
|
|
7015
|
+
<span style="font-size:12px;color:var(--muted);margin-left:4px">${statusBadge}</span>
|
|
7016
|
+
</div>
|
|
7017
|
+
|
|
6944
7018
|
<div class="load-section">
|
|
6945
|
-
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}
|
|
7019
|
+
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}</div>
|
|
6946
7020
|
<div class="load-config-row">
|
|
6947
7021
|
<div class="load-config-field" style="flex:1;min-width:200px">
|
|
6948
7022
|
<label>Base URL</label>
|
|
6949
|
-
<input id="loadBaseUrl" type="text" value="http://localhost:
|
|
7023
|
+
<input id="loadBaseUrl" type="text" value="http://localhost:5000" placeholder="http://localhost:5000" />
|
|
6950
7024
|
</div>
|
|
6951
7025
|
<div class="load-config-field">
|
|
6952
7026
|
<label>VUs</label>
|
|
@@ -6959,7 +7033,7 @@ function renderLoad(c) {
|
|
|
6959
7033
|
</div>
|
|
6960
7034
|
<div class="load-config-row" style="margin-top:8px">
|
|
6961
7035
|
<div class="load-config-field" style="flex:1;min-width:300px">
|
|
6962
|
-
<label>Bearer Token <span style="color:var(--dim);font-weight:400;font-size:11px">(→
|
|
7036
|
+
<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>
|
|
6963
7037
|
<input id="loadToken" type="password" placeholder="Вставь Bearer-токен (необязательно)" style="font-family:monospace;font-size:12px" oninput="localStorage.setItem('vr_load_token', this.value)" />
|
|
6964
7038
|
</div>
|
|
6965
7039
|
${featureOptions ? `<div class="load-config-field">
|
|
@@ -6979,9 +7053,9 @@ function renderLoad(c) {
|
|
|
6979
7053
|
|
|
6980
7054
|
<div class="load-section">
|
|
6981
7055
|
<div class="load-section-title">🤖 AI генерация скрипта</div>
|
|
6982
|
-
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Опиши сценарий — агент
|
|
7056
|
+
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Опиши сценарий — агент найдёт эндпоинты в коде и сгенерирует k6-скрипт</div>
|
|
6983
7057
|
<div class="load-ai-prompt-wrap">
|
|
6984
|
-
<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
|
|
7058
|
+
<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>
|
|
6985
7059
|
<button class="load-btn load-btn-ai" id="loadAiGenBtn" onclick="loadAiGenerateScript()" ${agentRunning ? 'disabled' : ''}>🤖 Сгенерировать<br>через AI</button>
|
|
6986
7060
|
</div>
|
|
6987
7061
|
<div class="load-ai-status" id="loadAiStatus">${loadAiGenerating ? '⏳ Агент генерирует скрипт… следи в терминале ↓' : ''}</div>
|
|
@@ -6991,31 +7065,9 @@ function renderLoad(c) {
|
|
|
6991
7065
|
<div class="load-section-title">k6 скрипт <span style="font-weight:400;color:var(--dim)">(редактируемый)</span></div>
|
|
6992
7066
|
<textarea class="load-script-editor" id="loadScriptEditor" spellcheck="false">${escapeHtml(loadScriptDraft)}</textarea>
|
|
6993
7067
|
<div class="load-save-row">
|
|
6994
|
-
<input class="load-save-input" id="loadScriptName" placeholder="
|
|
6995
|
-
<button class="load-btn" style="white-space:nowrap" onclick="loadSaveScript()">💾
|
|
6996
|
-
</div>
|
|
6997
|
-
</div>
|
|
6998
|
-
|
|
6999
|
-
<div class="load-section">
|
|
7000
|
-
<div class="load-section-title" style="display:flex;align-items:center;gap:8px">
|
|
7001
|
-
📁 Сохранённые скрипты
|
|
7002
|
-
<span style="font-size:11px;font-weight:400;color:var(--muted)">${loadSavedScripts.length} шт.</span>
|
|
7003
|
-
<button class="load-btn" style="margin-left:auto;padding:2px 8px;font-size:11px" onclick="loadRefreshScripts()">↻ обновить</button>
|
|
7068
|
+
<input class="load-save-input" id="loadScriptName" placeholder="Название, например: transcribe-audio-20vu" value="${escapeHtml(loadScriptNameDraft)}" />
|
|
7069
|
+
<button class="load-btn" style="white-space:nowrap" onclick="loadSaveScript()">💾 Сохранить</button>
|
|
7004
7070
|
</div>
|
|
7005
|
-
${loadSavedScripts.length === 0
|
|
7006
|
-
? `<div style="font-size:12px;color:var(--muted);padding:8px 0">Нет сохранённых скриптов — напиши скрипт выше и нажми «💾 Сохранить»</div>`
|
|
7007
|
-
: `<div class="load-scripts-list">
|
|
7008
|
-
${loadSavedScripts.map(s => `
|
|
7009
|
-
<div class="load-script-item">
|
|
7010
|
-
<div class="load-script-item-name">${escapeHtml(s.name)}</div>
|
|
7011
|
-
<div class="load-script-item-meta">${escapeHtml(s.date)}</div>
|
|
7012
|
-
<div class="load-script-item-actions">
|
|
7013
|
-
<button class="load-script-load-btn" onclick="loadLoadScript(${JSON.stringify(s.name)})">▶ Загрузить</button>
|
|
7014
|
-
<button class="load-script-del-btn" onclick="loadDeleteScript(${JSON.stringify(s.name)})">✕</button>
|
|
7015
|
-
</div>
|
|
7016
|
-
</div>`).join('')}
|
|
7017
|
-
</div>`
|
|
7018
|
-
}
|
|
7019
7071
|
</div>
|
|
7020
7072
|
|
|
7021
7073
|
${(isRunning || isDone || hasErr) ? `<div class="load-section">
|
|
@@ -7056,11 +7108,13 @@ function renderLoad(c) {
|
|
|
7056
7108
|
const logEl = document.getElementById('loadLogContent');
|
|
7057
7109
|
if (logEl) logEl.scrollTop = logEl.scrollHeight;
|
|
7058
7110
|
|
|
7111
|
+
// Restore token
|
|
7112
|
+
const savedToken = localStorage.getItem('vr_load_token');
|
|
7113
|
+
if (savedToken) { const el = document.getElementById('loadToken'); if (el) el.value = savedToken; }
|
|
7114
|
+
|
|
7059
7115
|
// Sync textarea with draft
|
|
7060
7116
|
const ta = document.getElementById('loadScriptEditor');
|
|
7061
|
-
if (ta) {
|
|
7062
|
-
ta.addEventListener('input', () => { loadScriptDraft = ta.value; });
|
|
7063
|
-
}
|
|
7117
|
+
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
7064
7118
|
|
|
7065
7119
|
// Feature selector auto-generates script
|
|
7066
7120
|
const featSel = document.getElementById('loadFeature');
|
|
@@ -7083,8 +7137,24 @@ function renderLoad(c) {
|
|
|
7083
7137
|
}
|
|
7084
7138
|
}
|
|
7085
7139
|
|
|
7140
|
+
function loadNewTest() {
|
|
7141
|
+
loadScriptDraft = '';
|
|
7142
|
+
loadScriptNameDraft = '';
|
|
7143
|
+
loadView = 'editor';
|
|
7144
|
+
renderContent();
|
|
7145
|
+
}
|
|
7146
|
+
|
|
7147
|
+
function loadOpenScript(name) {
|
|
7148
|
+
const s = loadSavedScripts.find(x => x.name === name);
|
|
7149
|
+
if (!s) return;
|
|
7150
|
+
loadScriptDraft = s.script;
|
|
7151
|
+
loadScriptNameDraft = s.name;
|
|
7152
|
+
loadView = 'editor';
|
|
7153
|
+
renderContent();
|
|
7154
|
+
}
|
|
7155
|
+
|
|
7086
7156
|
function loadGenerateScript() {
|
|
7087
|
-
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:
|
|
7157
|
+
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
7088
7158
|
const vus = parseInt(document.getElementById('loadVus')?.value || '10');
|
|
7089
7159
|
const duration = document.getElementById('loadDuration')?.value || '30s';
|
|
7090
7160
|
const featKey = document.getElementById('loadFeature')?.value || '';
|
|
@@ -7165,7 +7235,7 @@ async function loadAiGenerateScript() {
|
|
|
7165
7235
|
const userPrompt = promptEl ? promptEl.value.trim() : loadAiPromptDraft.trim();
|
|
7166
7236
|
if (!userPrompt) { alert('Опиши сценарий нагрузочного теста в поле выше'); return; }
|
|
7167
7237
|
|
|
7168
|
-
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:
|
|
7238
|
+
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
7169
7239
|
const vus = document.getElementById('loadVus')?.value || '10';
|
|
7170
7240
|
const duration = document.getElementById('loadDuration')?.value || '30s';
|
|
7171
7241
|
|
|
@@ -7257,7 +7327,8 @@ async function loadRefreshScripts() {
|
|
|
7257
7327
|
const r = await fetch('/api/load/scripts');
|
|
7258
7328
|
if (!r.ok) return;
|
|
7259
7329
|
loadSavedScripts = await r.json();
|
|
7260
|
-
|
|
7330
|
+
// Only re-render if we're on load tab in library view (don't disrupt editor)
|
|
7331
|
+
if (contextMode === 'load' && loadView === 'library') renderContent();
|
|
7261
7332
|
} catch {}
|
|
7262
7333
|
}
|
|
7263
7334
|
|