viberadar 0.3.174 → 0.3.176
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/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/probe/config.d.ts +2 -1
- package/dist/probe/config.d.ts.map +1 -1
- package/dist/probe/config.js +22 -12
- package/dist/probe/config.js.map +1 -1
- package/dist/probe/index.js +1 -1
- package/dist/probe/index.js.map +1 -1
- package/dist/probe/runner.d.ts.map +1 -1
- package/dist/probe/runner.js +34 -1
- package/dist/probe/runner.js.map +1 -1
- package/dist/probe/types.d.ts +2 -1
- package/dist/probe/types.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +163 -0
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +226 -1
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -1560,6 +1560,31 @@
|
|
|
1560
1560
|
.load-no-k6 h3 { color: var(--text); margin-bottom: 8px; }
|
|
1561
1561
|
.load-no-k6 code { background: var(--bg); border: 1px solid var(--border); padding: 3px 8px; border-radius: 4px; font-size: 13px; color: var(--blue); }
|
|
1562
1562
|
|
|
1563
|
+
/* Probe panel */
|
|
1564
|
+
.probe-container { padding: 20px 24px; max-width: 720px; }
|
|
1565
|
+
.probe-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 20px; gap: 12px; }
|
|
1566
|
+
.probe-title { font-size: 16px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
|
|
1567
|
+
.probe-section { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 14px; }
|
|
1568
|
+
.probe-section-title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
|
|
1569
|
+
.probe-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
1570
|
+
.probe-schedule-row { display: flex; align-items: center; gap: 8px; }
|
|
1571
|
+
.probe-select { padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 12px; cursor: pointer; }
|
|
1572
|
+
.probe-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 12px; cursor: pointer; transition: background 0.15s; white-space: nowrap; }
|
|
1573
|
+
.probe-btn:hover { background: var(--bg-hover); }
|
|
1574
|
+
.probe-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1575
|
+
.probe-btn-run { border-color: var(--green); color: var(--green); }
|
|
1576
|
+
.probe-btn-run:hover { background: rgba(63,185,80,0.1); }
|
|
1577
|
+
.probe-btn-start { border-color: var(--blue); color: var(--blue); }
|
|
1578
|
+
.probe-btn-start:hover { background: rgba(88,166,255,0.1); }
|
|
1579
|
+
.probe-btn-stop { border-color: var(--red); color: var(--red); }
|
|
1580
|
+
.probe-btn-stop:hover { background: rgba(248,81,73,0.1); }
|
|
1581
|
+
.probe-checks { display: flex; flex-direction: column; gap: 8px; }
|
|
1582
|
+
.probe-check-row { display: grid; grid-template-columns: 20px 1fr auto; align-items: baseline; gap: 6px; font-size: 13px; }
|
|
1583
|
+
.probe-check-icon { font-size: 14px; }
|
|
1584
|
+
.probe-check-name { color: var(--text); }
|
|
1585
|
+
.probe-check-dur { font-size: 11px; }
|
|
1586
|
+
.probe-check-error { grid-column: 2 / -1; font-size: 11px; color: var(--red); margin-top: 2px; word-break: break-word; }
|
|
1587
|
+
|
|
1563
1588
|
</style>
|
|
1564
1589
|
</head>
|
|
1565
1590
|
<body>
|
|
@@ -1672,6 +1697,8 @@
|
|
|
1672
1697
|
let D = null;
|
|
1673
1698
|
let contextMode = 'qa';
|
|
1674
1699
|
let view = 'features';
|
|
1700
|
+
let probeData = null; // { status, lastRun, intervalSec, nextRunAt, checks, configFound }
|
|
1701
|
+
let probeRunning = false;
|
|
1675
1702
|
let searchQuery = '';
|
|
1676
1703
|
let activeTypes = new Set();
|
|
1677
1704
|
let activePanelKey = null;
|
|
@@ -1780,13 +1807,14 @@ function switchMode(nextMode) {
|
|
|
1780
1807
|
saveModeState(contextMode);
|
|
1781
1808
|
contextMode = nextMode;
|
|
1782
1809
|
restoreModeState(contextMode);
|
|
1783
|
-
if (contextMode === 'observability' || contextMode === 'docs' || contextMode === 'services' || contextMode === 'load') {
|
|
1810
|
+
if (contextMode === 'observability' || contextMode === 'docs' || contextMode === 'services' || contextMode === 'load' || contextMode === 'probe') {
|
|
1784
1811
|
view = 'features';
|
|
1785
1812
|
drillFeatureKey = null;
|
|
1786
1813
|
drillTestType = null;
|
|
1787
1814
|
activePanelKey = null;
|
|
1788
1815
|
clearFeatureHash();
|
|
1789
1816
|
}
|
|
1817
|
+
if (contextMode === 'probe') { loadProbeData(); }
|
|
1790
1818
|
if (contextMode === 'load' && loadK6Available === null) { checkK6(); }
|
|
1791
1819
|
if (contextMode === 'load') {
|
|
1792
1820
|
loadRefreshScripts();
|
|
@@ -3155,6 +3183,7 @@ function renderModeSwitch() {
|
|
|
3155
3183
|
{ key: 'scenarios', label: 'Сценарии', hint: 'Пользовательские сценарии, user journeys' },
|
|
3156
3184
|
{ key: 'services', label: 'Карта сервисов', hint: 'Зависимости, пайплайны, мониторинг' },
|
|
3157
3185
|
{ key: 'load', label: 'Нагрузка', hint: 'k6: метрики, сценарии, AI-анализ' },
|
|
3186
|
+
{ key: 'probe', label: 'Probe', hint: 'Synthetic monitoring, E2E на стенде' },
|
|
3158
3187
|
];
|
|
3159
3188
|
root.innerHTML = modes.map(m => `
|
|
3160
3189
|
<button class="mode-switch-btn ${contextMode === m.key ? 'active' : ''}" data-mode="${m.key}">
|
|
@@ -3173,6 +3202,16 @@ function renderSidebar() {
|
|
|
3173
3202
|
const tabs = document.getElementById('viewTabs');
|
|
3174
3203
|
const extra = document.getElementById('sidebarExtra');
|
|
3175
3204
|
|
|
3205
|
+
if (contextMode === 'probe') {
|
|
3206
|
+
tabs.style.display = 'none';
|
|
3207
|
+
extra.innerHTML = `
|
|
3208
|
+
<div class="sidebar-label">Probe</div>
|
|
3209
|
+
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45">
|
|
3210
|
+
Запуск E2E-проверок на стенде. Домен задаётся в Настройках.
|
|
3211
|
+
</div>`;
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3176
3215
|
if (contextMode === 'observability') {
|
|
3177
3216
|
tabs.style.display = 'none';
|
|
3178
3217
|
extra.innerHTML = `
|
|
@@ -3318,6 +3357,11 @@ function renderContent() {
|
|
|
3318
3357
|
return;
|
|
3319
3358
|
}
|
|
3320
3359
|
|
|
3360
|
+
if (contextMode === 'probe') {
|
|
3361
|
+
renderProbePanel(c);
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3321
3365
|
if (view === 'features') {
|
|
3322
3366
|
if (drillFeatureKey === '__unmapped__') renderUnmappedDetail(c);
|
|
3323
3367
|
else if (drillFeatureKey) renderFeatureDetail(c);
|
|
@@ -3329,6 +3373,161 @@ function renderContent() {
|
|
|
3329
3373
|
}
|
|
3330
3374
|
}
|
|
3331
3375
|
|
|
3376
|
+
// ─── Probe rendering ────────────────────────────────────────────────────────
|
|
3377
|
+
|
|
3378
|
+
async function loadProbeData() {
|
|
3379
|
+
try {
|
|
3380
|
+
const res = await fetch('/api/probe/status');
|
|
3381
|
+
probeData = await res.json();
|
|
3382
|
+
} catch {}
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
function renderProbePanel(c) {
|
|
3386
|
+
const d = probeData;
|
|
3387
|
+
const statusColor = !d ? 'var(--muted)' : d.status === 'running' ? 'var(--yellow)' : d.status === 'scheduled' ? 'var(--green)' : 'var(--muted)';
|
|
3388
|
+
const statusLabel = !d ? '—' : d.status === 'running' ? '● running' : d.status === 'scheduled' ? '● scheduled' : '○ idle';
|
|
3389
|
+
|
|
3390
|
+
const lastRunHtml = d && d.lastRun ? (() => {
|
|
3391
|
+
const r = d.lastRun;
|
|
3392
|
+
const allPassed = r.failed === 0;
|
|
3393
|
+
const rows = r.results.map(res => {
|
|
3394
|
+
const ok = res.status === 'passed';
|
|
3395
|
+
return `<div class="probe-check-row">
|
|
3396
|
+
<span class="probe-check-icon">${ok ? '✅' : '❌'}</span>
|
|
3397
|
+
<span class="probe-check-name">${escapeHtml(res.check)}</span>
|
|
3398
|
+
<span class="probe-check-dur" style="color:var(--muted)">${res.durationMs}ms</span>
|
|
3399
|
+
${!ok && res.error ? `<div class="probe-check-error">${escapeHtml(res.error)}</div>` : ''}
|
|
3400
|
+
${!ok && res.screenshotPath ? `<div class="probe-check-error" style="color:var(--muted)">📸 ${escapeHtml(res.screenshotPath)}</div>` : ''}
|
|
3401
|
+
</div>`;
|
|
3402
|
+
}).join('');
|
|
3403
|
+
return `
|
|
3404
|
+
<div class="probe-section">
|
|
3405
|
+
<div class="probe-section-title">Последний прогон <span style="color:var(--muted);font-weight:400;font-size:11px">${r.timestamp ? new Date(r.timestamp).toLocaleTimeString() : ''}</span>
|
|
3406
|
+
<span style="margin-left:8px;font-size:12px">${allPassed ? `<span style="color:var(--green)">✅ ${r.passed}/${r.results.length}</span>` : `<span style="color:var(--red)">❌ ${r.failed} failed</span>`}</span>
|
|
3407
|
+
</div>
|
|
3408
|
+
<div class="probe-checks">${rows}</div>
|
|
3409
|
+
</div>`;
|
|
3410
|
+
})() : `<div style="color:var(--muted);font-size:13px;padding:16px 0">Ещё не запускался</div>`;
|
|
3411
|
+
|
|
3412
|
+
const intervalOptions = [60, 300, 600, 1800, 3600].map(s => {
|
|
3413
|
+
const label = s < 3600 ? `каждые ${s / 60} мин` : `каждый час`;
|
|
3414
|
+
const sel = d && d.intervalSec === s ? ' selected' : '';
|
|
3415
|
+
return `<option value="${s}"${sel}>${label}</option>`;
|
|
3416
|
+
}).join('');
|
|
3417
|
+
|
|
3418
|
+
const isScheduled = d && d.status === 'scheduled';
|
|
3419
|
+
const isRunning = d && d.status === 'running';
|
|
3420
|
+
|
|
3421
|
+
c.innerHTML = `
|
|
3422
|
+
<div class="probe-container">
|
|
3423
|
+
<div class="probe-header">
|
|
3424
|
+
<div>
|
|
3425
|
+
<div class="probe-title">🔭 Probe monitoring</div>
|
|
3426
|
+
${d && d.effectiveTarget ? `<div style="color:var(--muted);font-size:12px">${escapeHtml(d.effectiveTarget)} · ${escapeHtml((d.checks || []).length)} checks · <span style="color:${statusColor}">${statusLabel}</span></div>`
|
|
3427
|
+
: `<div style="color:var(--yellow);font-size:12px">⚠️ Задайте домен стенда в Настройках</div>`}
|
|
3428
|
+
</div>
|
|
3429
|
+
<button class="probe-btn" onclick="openProbeSettingsModal()">⚙️ Настройки</button>
|
|
3430
|
+
</div>
|
|
3431
|
+
|
|
3432
|
+
<div class="probe-section">
|
|
3433
|
+
<div class="probe-controls">
|
|
3434
|
+
<button class="probe-btn probe-btn-run" onclick="probeRunNow()" ${isRunning ? 'disabled' : ''}>
|
|
3435
|
+
${isRunning ? '⏳ Выполняется…' : '▶ Run Now'}
|
|
3436
|
+
</button>
|
|
3437
|
+
<div class="probe-schedule-row">
|
|
3438
|
+
<select id="probeIntervalSelect" class="probe-select">
|
|
3439
|
+
${intervalOptions}
|
|
3440
|
+
</select>
|
|
3441
|
+
${isScheduled
|
|
3442
|
+
? `<button class="probe-btn probe-btn-stop" onclick="probeScheduleStop()">⏹ Стоп</button>`
|
|
3443
|
+
: `<button class="probe-btn probe-btn-start" onclick="probeScheduleStart()">🕐 По расписанию</button>`
|
|
3444
|
+
}
|
|
3445
|
+
</div>
|
|
3446
|
+
</div>
|
|
3447
|
+
${isScheduled && d.nextRunAt ? `<div style="font-size:11px;color:var(--muted);margin-top:6px">Следующий запуск: ${new Date(d.nextRunAt).toLocaleTimeString()}</div>` : ''}
|
|
3448
|
+
</div>
|
|
3449
|
+
|
|
3450
|
+
${lastRunHtml}
|
|
3451
|
+
</div>`;
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
async function probeRunNow() {
|
|
3455
|
+
probeRunning = true;
|
|
3456
|
+
renderContent();
|
|
3457
|
+
try { await fetch('/api/probe/run', { method: 'POST' }); } catch {}
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
async function probeScheduleStart() {
|
|
3461
|
+
const sel = document.getElementById('probeIntervalSelect');
|
|
3462
|
+
const intervalSec = sel ? parseInt(sel.value) : 600;
|
|
3463
|
+
await fetch('/api/probe/schedule/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ intervalSec }) });
|
|
3464
|
+
await loadProbeData();
|
|
3465
|
+
renderContent();
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
async function probeScheduleStop() {
|
|
3469
|
+
await fetch('/api/probe/schedule/stop', { method: 'POST' });
|
|
3470
|
+
await loadProbeData();
|
|
3471
|
+
renderContent();
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
function openProbeSettingsModal() {
|
|
3475
|
+
fetch('/api/probe/settings').then(r => r.json()).then(saved => {
|
|
3476
|
+
const tg = saved.telegram || {};
|
|
3477
|
+
const inp = s => `style="width:100%;box-sizing:border-box;padding:8px 12px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:13px"`;
|
|
3478
|
+
const lbl = t => `<label style="font-size:12px;color:var(--muted);display:block;margin:12px 0 4px">${t}</label>`;
|
|
3479
|
+
let overlay = document.getElementById('probeSettingsOverlay');
|
|
3480
|
+
if (overlay) overlay.remove();
|
|
3481
|
+
overlay = document.createElement('div');
|
|
3482
|
+
overlay.id = 'probeSettingsOverlay';
|
|
3483
|
+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999';
|
|
3484
|
+
overlay.innerHTML = `
|
|
3485
|
+
<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px 28px;width:440px;max-width:90vw">
|
|
3486
|
+
<h3 style="margin:0 0 4px;font-size:16px;color:var(--text)">⚙️ Настройки Probe</h3>
|
|
3487
|
+
<div style="font-size:12px;color:var(--muted);margin-bottom:16px">Настройки сохраняются локально, не попадают в git</div>
|
|
3488
|
+
|
|
3489
|
+
<div style="border-bottom:1px solid var(--border);padding-bottom:16px;margin-bottom:4px">
|
|
3490
|
+
${lbl('Домен стенда')}
|
|
3491
|
+
<input id="probeTarget" type="text" placeholder="https://staging.myapp.com" value="${escapeHtml(saved.target || '')}" ${inp()}>
|
|
3492
|
+
<div style="font-size:11px;color:var(--muted);margin-top:4px">Все проверки будут запускаться на этом домене. Переопределяет target из probe.config.yml</div>
|
|
3493
|
+
</div>
|
|
3494
|
+
|
|
3495
|
+
<div style="margin-top:16px">
|
|
3496
|
+
<div style="font-size:12px;font-weight:600;color:var(--text);margin-bottom:8px">Telegram уведомления</div>
|
|
3497
|
+
${lbl('Bot Token')}
|
|
3498
|
+
<input id="probeBotToken" type="password" placeholder="1234567890:AAF..." value="${escapeHtml(tg.botToken || '')}" ${inp()}>
|
|
3499
|
+
${lbl('Chat ID')}
|
|
3500
|
+
<input id="probeChatId" type="text" placeholder="-1001234567890" value="${escapeHtml(tg.chatId || '')}" ${inp()}>
|
|
3501
|
+
<div style="font-size:11px;color:var(--muted);margin-top:4px">Токен у @BotFather · Chat ID через @userinfobot</div>
|
|
3502
|
+
</div>
|
|
3503
|
+
|
|
3504
|
+
<div id="probeSettingsErr" style="display:none;margin-top:12px;padding:8px 12px;border-radius:6px;background:rgba(248,81,73,0.1);color:var(--red);font-size:12px"></div>
|
|
3505
|
+
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:20px">
|
|
3506
|
+
<button onclick="document.getElementById('probeSettingsOverlay').remove()" style="padding:6px 16px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);cursor:pointer;font-size:13px">Отмена</button>
|
|
3507
|
+
<button id="probeSettingsSave" style="padding:6px 16px;border-radius:6px;border:1px solid var(--blue);background:var(--blue);color:#fff;cursor:pointer;font-size:13px">Сохранить</button>
|
|
3508
|
+
</div>
|
|
3509
|
+
</div>`;
|
|
3510
|
+
document.body.appendChild(overlay);
|
|
3511
|
+
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
|
|
3512
|
+
document.getElementById('probeSettingsSave').addEventListener('click', async () => {
|
|
3513
|
+
const target = document.getElementById('probeTarget').value.trim();
|
|
3514
|
+
const botToken = document.getElementById('probeBotToken').value.trim();
|
|
3515
|
+
const chatId = document.getElementById('probeChatId').value.trim();
|
|
3516
|
+
const err = document.getElementById('probeSettingsErr');
|
|
3517
|
+
const btn = document.getElementById('probeSettingsSave');
|
|
3518
|
+
btn.disabled = true; btn.textContent = 'Сохраняю…';
|
|
3519
|
+
try {
|
|
3520
|
+
const res = await fetch('/api/probe/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ target, botToken, chatId }) });
|
|
3521
|
+
if (!res.ok) { const d = await res.json(); throw new Error(d.error || 'Ошибка сохранения'); }
|
|
3522
|
+
// Refresh probe data to show new target
|
|
3523
|
+
await loadProbeData();
|
|
3524
|
+
renderContent();
|
|
3525
|
+
overlay.remove();
|
|
3526
|
+
} catch (e) { err.style.display = 'block'; err.textContent = e.message; btn.disabled = false; btn.textContent = 'Сохранить'; }
|
|
3527
|
+
});
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3332
3531
|
// ─── Service Map rendering ──────────────────────────────────────────────────
|
|
3333
3532
|
|
|
3334
3533
|
const SVC_CATEGORY_COLORS = {
|
|
@@ -6735,6 +6934,7 @@ document.querySelectorAll('.view-tab').forEach(tab => {
|
|
|
6735
6934
|
document.getElementById('searchInput').value = '';
|
|
6736
6935
|
document.getElementById('panel').classList.remove('open');
|
|
6737
6936
|
if (view !== 'features') clearFeatureHash();
|
|
6937
|
+
if (view === 'probe') { loadProbeData().then(() => { renderSidebar(); renderContent(); }); return; }
|
|
6738
6938
|
renderSidebar();
|
|
6739
6939
|
renderContent();
|
|
6740
6940
|
};
|
|
@@ -7791,6 +7991,31 @@ function connectSSE() {
|
|
|
7791
7991
|
if (contextMode === 'load') { renderSidebar(); renderContent(); }
|
|
7792
7992
|
});
|
|
7793
7993
|
|
|
7994
|
+
es.addEventListener('probe-run-started', () => {
|
|
7995
|
+
if (!probeData) probeData = {};
|
|
7996
|
+
probeData.status = 'running';
|
|
7997
|
+
probeRunning = true;
|
|
7998
|
+
if (view === 'probe') renderContent();
|
|
7999
|
+
});
|
|
8000
|
+
|
|
8001
|
+
es.addEventListener('probe-run-done', (e) => {
|
|
8002
|
+
const payload = JSON.parse(e.data || '{}');
|
|
8003
|
+
if (!probeData) probeData = {};
|
|
8004
|
+
if (payload.results) probeData.lastRun = payload;
|
|
8005
|
+
probeData.status = probeData.intervalSec ? 'scheduled' : 'idle';
|
|
8006
|
+
probeRunning = false;
|
|
8007
|
+
if (view === 'probe') renderContent();
|
|
8008
|
+
});
|
|
8009
|
+
|
|
8010
|
+
es.addEventListener('probe-scheduled', (e) => {
|
|
8011
|
+
const payload = JSON.parse(e.data || '{}');
|
|
8012
|
+
if (!probeData) probeData = {};
|
|
8013
|
+
probeData.status = payload.status;
|
|
8014
|
+
probeData.intervalSec = payload.intervalSec;
|
|
8015
|
+
probeData.nextRunAt = payload.nextRunAt;
|
|
8016
|
+
if (view === 'probe') renderContent();
|
|
8017
|
+
});
|
|
8018
|
+
|
|
7794
8019
|
es.onerror = () => {
|
|
7795
8020
|
setLiveDot('var(--dim)', 'Нет соединения — переподключаюсь…');
|
|
7796
8021
|
es.close();
|