viberadar 0.3.164 → 0.3.165
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 +114 -45
- 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,56 @@ 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" onclick="loadOpenScript(${JSON.stringify(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 class="load-library-card-actions">
|
|
6964
|
+
<button class="load-script-load-btn" onclick="event.stopPropagation();loadOpenScript(${JSON.stringify(s.name)})">▶ Открыть</button>
|
|
6965
|
+
<button class="load-script-del-btn" onclick="event.stopPropagation();loadDeleteScript(${JSON.stringify(s.name)})">✕</button>
|
|
6966
|
+
</div>
|
|
6967
|
+
</div>`).join('');
|
|
6968
|
+
|
|
6969
|
+
c.innerHTML = `<div class="load-screen">
|
|
6970
|
+
<div class="load-library-header">
|
|
6971
|
+
<div>
|
|
6972
|
+
<div style="font-size:16px;font-weight:600;color:var(--fg)">Нагрузочные тесты</div>
|
|
6973
|
+
<div style="font-size:12px;color:var(--muted);margin-top:2px">${loadK6Version ? escapeHtml(loadK6Version) : ''} · ${loadSavedScripts.length} сохранённых</div>
|
|
6974
|
+
</div>
|
|
6975
|
+
<button class="load-btn load-btn-run" style="font-size:13px;padding:8px 20px" onclick="loadNewTest()">+ Новый тест</button>
|
|
6976
|
+
</div>
|
|
6977
|
+
|
|
6978
|
+
${loadSavedScripts.length === 0
|
|
6979
|
+
? `<div class="load-library-empty">
|
|
6980
|
+
<div style="font-size:32px;margin-bottom:12px">📋</div>
|
|
6981
|
+
<div style="font-size:14px;font-weight:500;margin-bottom:6px">Нет сохранённых тестов</div>
|
|
6982
|
+
<div style="font-size:12px;color:var(--muted);margin-bottom:16px">Создай первый тест — напиши k6 скрипт или сгенерируй через AI</div>
|
|
6983
|
+
<button class="load-btn load-btn-run" onclick="loadNewTest()">+ Создать первый тест</button>
|
|
6984
|
+
</div>`
|
|
6985
|
+
: `<div class="load-library-grid">${cards}</div>`
|
|
6986
|
+
}
|
|
6987
|
+
</div>`;
|
|
6988
|
+
return;
|
|
6989
|
+
}
|
|
6990
|
+
|
|
6991
|
+
// ─── EDITOR VIEW ──────────────────────────────────────────────────────────
|
|
6923
6992
|
const features = (D && D.features) || [];
|
|
6924
6993
|
const featureOptions = features.map(f =>
|
|
6925
6994
|
`<option value="${f.key}">${f.label || f.key}</option>`
|
|
6926
6995
|
).join('');
|
|
6927
6996
|
|
|
6928
|
-
// Build script draft if empty
|
|
6929
6997
|
if (!loadScriptDraft) {
|
|
6930
|
-
loadScriptDraft = buildDefaultScript({ baseUrl: 'http://localhost:
|
|
6998
|
+
loadScriptDraft = buildDefaultScript({ baseUrl: 'http://localhost:5000', vus: 10, duration: '30s', endpoints: ['/'] });
|
|
6931
6999
|
}
|
|
6932
7000
|
|
|
6933
7001
|
const statusBadge = !loadState || loadState.status === 'idle' ? '' :
|
|
@@ -6941,12 +7009,17 @@ function renderLoad(c) {
|
|
|
6941
7009
|
|
|
6942
7010
|
c.innerHTML = `<div class="load-screen">
|
|
6943
7011
|
|
|
7012
|
+
<div class="load-editor-topbar">
|
|
7013
|
+
${!isRunning ? `<button class="load-back-btn" onclick="loadView='library';renderContent()">← Все тесты</button>` : ''}
|
|
7014
|
+
<span style="font-size:12px;color:var(--muted);margin-left:4px">${statusBadge}</span>
|
|
7015
|
+
</div>
|
|
7016
|
+
|
|
6944
7017
|
<div class="load-section">
|
|
6945
|
-
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}
|
|
7018
|
+
<div class="load-section-title">Конфигурация ${loadK6Version ? `<span style="color:var(--dim);font-weight:400">${escapeHtml(loadK6Version)}</span>` : ''}</div>
|
|
6946
7019
|
<div class="load-config-row">
|
|
6947
7020
|
<div class="load-config-field" style="flex:1;min-width:200px">
|
|
6948
7021
|
<label>Base URL</label>
|
|
6949
|
-
<input id="loadBaseUrl" type="text" value="http://localhost:
|
|
7022
|
+
<input id="loadBaseUrl" type="text" value="http://localhost:5000" placeholder="http://localhost:5000" />
|
|
6950
7023
|
</div>
|
|
6951
7024
|
<div class="load-config-field">
|
|
6952
7025
|
<label>VUs</label>
|
|
@@ -6959,7 +7032,7 @@ function renderLoad(c) {
|
|
|
6959
7032
|
</div>
|
|
6960
7033
|
<div class="load-config-row" style="margin-top:8px">
|
|
6961
7034
|
<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">(→
|
|
7035
|
+
<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
7036
|
<input id="loadToken" type="password" placeholder="Вставь Bearer-токен (необязательно)" style="font-family:monospace;font-size:12px" oninput="localStorage.setItem('vr_load_token', this.value)" />
|
|
6964
7037
|
</div>
|
|
6965
7038
|
${featureOptions ? `<div class="load-config-field">
|
|
@@ -6979,9 +7052,9 @@ function renderLoad(c) {
|
|
|
6979
7052
|
|
|
6980
7053
|
<div class="load-section">
|
|
6981
7054
|
<div class="load-section-title">🤖 AI генерация скрипта</div>
|
|
6982
|
-
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Опиши сценарий — агент
|
|
7055
|
+
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Опиши сценарий — агент найдёт эндпоинты в коде и сгенерирует k6-скрипт</div>
|
|
6983
7056
|
<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
|
|
7057
|
+
<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
7058
|
<button class="load-btn load-btn-ai" id="loadAiGenBtn" onclick="loadAiGenerateScript()" ${agentRunning ? 'disabled' : ''}>🤖 Сгенерировать<br>через AI</button>
|
|
6986
7059
|
</div>
|
|
6987
7060
|
<div class="load-ai-status" id="loadAiStatus">${loadAiGenerating ? '⏳ Агент генерирует скрипт… следи в терминале ↓' : ''}</div>
|
|
@@ -6991,31 +7064,9 @@ function renderLoad(c) {
|
|
|
6991
7064
|
<div class="load-section-title">k6 скрипт <span style="font-weight:400;color:var(--dim)">(редактируемый)</span></div>
|
|
6992
7065
|
<textarea class="load-script-editor" id="loadScriptEditor" spellcheck="false">${escapeHtml(loadScriptDraft)}</textarea>
|
|
6993
7066
|
<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>
|
|
7067
|
+
<input class="load-save-input" id="loadScriptName" placeholder="Название, например: transcribe-audio-20vu" value="${escapeHtml(loadScriptNameDraft)}" />
|
|
7068
|
+
<button class="load-btn" style="white-space:nowrap" onclick="loadSaveScript()">💾 Сохранить</button>
|
|
7004
7069
|
</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
7070
|
</div>
|
|
7020
7071
|
|
|
7021
7072
|
${(isRunning || isDone || hasErr) ? `<div class="load-section">
|
|
@@ -7056,11 +7107,13 @@ function renderLoad(c) {
|
|
|
7056
7107
|
const logEl = document.getElementById('loadLogContent');
|
|
7057
7108
|
if (logEl) logEl.scrollTop = logEl.scrollHeight;
|
|
7058
7109
|
|
|
7110
|
+
// Restore token
|
|
7111
|
+
const savedToken = localStorage.getItem('vr_load_token');
|
|
7112
|
+
if (savedToken) { const el = document.getElementById('loadToken'); if (el) el.value = savedToken; }
|
|
7113
|
+
|
|
7059
7114
|
// Sync textarea with draft
|
|
7060
7115
|
const ta = document.getElementById('loadScriptEditor');
|
|
7061
|
-
if (ta) {
|
|
7062
|
-
ta.addEventListener('input', () => { loadScriptDraft = ta.value; });
|
|
7063
|
-
}
|
|
7116
|
+
if (ta) { ta.addEventListener('input', () => { loadScriptDraft = ta.value; }); }
|
|
7064
7117
|
|
|
7065
7118
|
// Feature selector auto-generates script
|
|
7066
7119
|
const featSel = document.getElementById('loadFeature');
|
|
@@ -7083,8 +7136,24 @@ function renderLoad(c) {
|
|
|
7083
7136
|
}
|
|
7084
7137
|
}
|
|
7085
7138
|
|
|
7139
|
+
function loadNewTest() {
|
|
7140
|
+
loadScriptDraft = '';
|
|
7141
|
+
loadScriptNameDraft = '';
|
|
7142
|
+
loadView = 'editor';
|
|
7143
|
+
renderContent();
|
|
7144
|
+
}
|
|
7145
|
+
|
|
7146
|
+
function loadOpenScript(name) {
|
|
7147
|
+
const s = loadSavedScripts.find(x => x.name === name);
|
|
7148
|
+
if (!s) return;
|
|
7149
|
+
loadScriptDraft = s.script;
|
|
7150
|
+
loadScriptNameDraft = s.name;
|
|
7151
|
+
loadView = 'editor';
|
|
7152
|
+
renderContent();
|
|
7153
|
+
}
|
|
7154
|
+
|
|
7086
7155
|
function loadGenerateScript() {
|
|
7087
|
-
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:
|
|
7156
|
+
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
7088
7157
|
const vus = parseInt(document.getElementById('loadVus')?.value || '10');
|
|
7089
7158
|
const duration = document.getElementById('loadDuration')?.value || '30s';
|
|
7090
7159
|
const featKey = document.getElementById('loadFeature')?.value || '';
|
|
@@ -7165,7 +7234,7 @@ async function loadAiGenerateScript() {
|
|
|
7165
7234
|
const userPrompt = promptEl ? promptEl.value.trim() : loadAiPromptDraft.trim();
|
|
7166
7235
|
if (!userPrompt) { alert('Опиши сценарий нагрузочного теста в поле выше'); return; }
|
|
7167
7236
|
|
|
7168
|
-
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:
|
|
7237
|
+
const baseUrl = document.getElementById('loadBaseUrl')?.value || 'http://localhost:5000';
|
|
7169
7238
|
const vus = document.getElementById('loadVus')?.value || '10';
|
|
7170
7239
|
const duration = document.getElementById('loadDuration')?.value || '30s';
|
|
7171
7240
|
|