viberadar 0.3.178 → 0.3.180
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/probe/runner.d.ts +7 -2
- package/dist/probe/runner.d.ts.map +1 -1
- package/dist/probe/runner.js +10 -4
- package/dist/probe/runner.js.map +1 -1
- package/dist/probe/types.d.ts +1 -0
- package/dist/probe/types.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +42 -9
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +214 -52
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -1561,8 +1561,8 @@
|
|
|
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
1563
|
/* Probe panel */
|
|
1564
|
-
.probe-container { padding: 20px 24px; max-width:
|
|
1565
|
-
.probe-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom:
|
|
1564
|
+
.probe-container { padding: 20px 24px; max-width: 960px; }
|
|
1565
|
+
.probe-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 16px; gap: 12px; }
|
|
1566
1566
|
.probe-title { font-size: 16px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
|
|
1567
1567
|
.probe-section { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 14px; }
|
|
1568
1568
|
.probe-section-title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; }
|
|
@@ -1578,12 +1578,40 @@
|
|
|
1578
1578
|
.probe-btn-start:hover { background: rgba(88,166,255,0.1); }
|
|
1579
1579
|
.probe-btn-stop { border-color: var(--red); color: var(--red); }
|
|
1580
1580
|
.probe-btn-stop:hover { background: rgba(248,81,73,0.1); }
|
|
1581
|
-
|
|
1582
|
-
.probe-
|
|
1583
|
-
.probe-
|
|
1584
|
-
.probe-
|
|
1585
|
-
.probe-
|
|
1586
|
-
.probe-
|
|
1581
|
+
/* Probe cards */
|
|
1582
|
+
.probe-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; margin-top: 16px; }
|
|
1583
|
+
.probe-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; cursor: pointer; transition: border-color 0.15s, background 0.15s; display: flex; flex-direction: column; gap: 0; }
|
|
1584
|
+
.probe-card:hover { border-color: var(--blue); background: var(--bg-hover); }
|
|
1585
|
+
.probe-card.running { border-color: var(--yellow, #e3b341); }
|
|
1586
|
+
.probe-card.passed { border-color: var(--green); }
|
|
1587
|
+
.probe-card.failed { border-color: var(--red); }
|
|
1588
|
+
.probe-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
1589
|
+
.probe-card-name { font-size: 13px; font-weight: 600; color: var(--text); flex: 1; line-height: 1.4; }
|
|
1590
|
+
.probe-card-icon { font-size: 20px; flex-shrink: 0; }
|
|
1591
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
1592
|
+
.probe-spin { display: inline-block; animation: spin 1s linear infinite; }
|
|
1593
|
+
.probe-card-meta { display: flex; gap: 6px; align-items: center; margin-top: 6px; flex-wrap: wrap; }
|
|
1594
|
+
.probe-badge { background: var(--bg); border: 1px solid var(--border); border-radius: 3px; padding: 1px 5px; font-size: 10px; color: var(--muted); }
|
|
1595
|
+
.probe-card-dur { font-size: 11px; color: var(--muted); }
|
|
1596
|
+
.probe-card-err { font-size: 11px; color: var(--red); margin-top: 6px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; }
|
|
1597
|
+
.probe-card-footer { display: flex; align-items: center; justify-content: flex-end; margin-top: 12px; }
|
|
1598
|
+
/* Probe detail */
|
|
1599
|
+
.probe-detail { padding: 20px 24px; max-width: 860px; }
|
|
1600
|
+
.probe-back { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--blue); cursor: pointer; margin-bottom: 16px; }
|
|
1601
|
+
.probe-back:hover { text-decoration: underline; }
|
|
1602
|
+
.probe-detail-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
|
|
1603
|
+
.probe-detail-title { font-size: 18px; font-weight: 700; color: var(--text); }
|
|
1604
|
+
.probe-detail-meta { font-size: 12px; color: var(--muted); margin-top: 4px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
|
1605
|
+
.probe-status-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; }
|
|
1606
|
+
.probe-status-passed { background: rgba(63,185,80,0.12); color: var(--green); }
|
|
1607
|
+
.probe-status-failed { background: rgba(248,81,73,0.12); color: var(--red); }
|
|
1608
|
+
.probe-status-running { background: rgba(227,179,65,0.12); color: #e3b341; }
|
|
1609
|
+
.probe-status-idle { background: var(--bg); color: var(--muted); border: 1px solid var(--border); }
|
|
1610
|
+
.probe-steps-list { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
|
|
1611
|
+
.probe-step-row { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 7px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; }
|
|
1612
|
+
.probe-step-type { font-size: 10px; font-weight: 600; color: var(--blue); background: rgba(88,166,255,0.1); padding: 1px 5px; border-radius: 3px; min-width: 80px; text-align: center; }
|
|
1613
|
+
.probe-step-val { color: var(--text); font-family: monospace; }
|
|
1614
|
+
.probe-output-box { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 11px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 12px 14px; white-space: pre-wrap; overflow-x: auto; max-height: 360px; overflow-y: auto; color: var(--text); line-height: 1.5; margin-top: 8px; }
|
|
1587
1615
|
|
|
1588
1616
|
</style>
|
|
1589
1617
|
</head>
|
|
@@ -1697,8 +1725,9 @@
|
|
|
1697
1725
|
let D = null;
|
|
1698
1726
|
let contextMode = 'qa';
|
|
1699
1727
|
let view = 'features';
|
|
1700
|
-
let probeData = null; // { status, lastRun, intervalSec, nextRunAt, checks, configFound }
|
|
1728
|
+
let probeData = null; // { status, lastRun, intervalSec, nextRunAt, checks, configFound, checkResults, runningCheck }
|
|
1701
1729
|
let probeRunning = false;
|
|
1730
|
+
let probeDetailCheck = null; // null = cards view, string = check name being viewed
|
|
1702
1731
|
let searchQuery = '';
|
|
1703
1732
|
let activeTypes = new Set();
|
|
1704
1733
|
let activePanelKey = null;
|
|
@@ -1749,6 +1778,7 @@ const modeStore = {
|
|
|
1749
1778
|
scenarios: { view: 'list', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
1750
1779
|
services: { view: 'graph', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null, svcTab: 'graph' },
|
|
1751
1780
|
load: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
1781
|
+
probe: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
1752
1782
|
};
|
|
1753
1783
|
|
|
1754
1784
|
function getModeFromPath(pathname = window.location.pathname) {
|
|
@@ -1756,6 +1786,7 @@ function getModeFromPath(pathname = window.location.pathname) {
|
|
|
1756
1786
|
if (pathname.startsWith('/radar/docs')) return 'docs';
|
|
1757
1787
|
if (pathname.startsWith('/radar/services')) return 'services';
|
|
1758
1788
|
if (pathname.startsWith('/radar/load')) return 'load';
|
|
1789
|
+
if (pathname.startsWith('/radar/probe')) return 'probe';
|
|
1759
1790
|
return 'qa';
|
|
1760
1791
|
}
|
|
1761
1792
|
|
|
@@ -1764,6 +1795,7 @@ function routePathForMode(mode) {
|
|
|
1764
1795
|
if (mode === 'docs') return '/radar/docs';
|
|
1765
1796
|
if (mode === 'services') return '/radar/services';
|
|
1766
1797
|
if (mode === 'load') return '/radar/load';
|
|
1798
|
+
if (mode === 'probe') return '/radar/probe';
|
|
1767
1799
|
return '/radar/qa';
|
|
1768
1800
|
}
|
|
1769
1801
|
|
|
@@ -3382,79 +3414,190 @@ async function loadProbeData() {
|
|
|
3382
3414
|
} catch {}
|
|
3383
3415
|
}
|
|
3384
3416
|
|
|
3417
|
+
function probeCheckStatus(checkName) {
|
|
3418
|
+
if (!probeData) return 'idle';
|
|
3419
|
+
if (probeData.runningCheck === checkName) return 'running';
|
|
3420
|
+
const r = probeData.checkResults?.[checkName];
|
|
3421
|
+
return r ? r.status : 'idle';
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
function probeCheckResult(checkName) {
|
|
3425
|
+
return probeData?.checkResults?.[checkName] || null;
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
function probeStatusIcon(status) {
|
|
3429
|
+
if (status === 'running') return `<span class="probe-spin">⏳</span>`;
|
|
3430
|
+
if (status === 'passed') return '✅';
|
|
3431
|
+
if (status === 'failed') return '❌';
|
|
3432
|
+
return '⏸';
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
function probeStatusBadge(status) {
|
|
3436
|
+
if (status === 'running') return `<span class="probe-status-badge probe-status-running"><span class="probe-spin">⏳</span> Выполняется</span>`;
|
|
3437
|
+
if (status === 'passed') return `<span class="probe-status-badge probe-status-passed">✅ Passed</span>`;
|
|
3438
|
+
if (status === 'failed') return `<span class="probe-status-badge probe-status-failed">❌ Failed</span>`;
|
|
3439
|
+
return `<span class="probe-status-badge probe-status-idle">⏸ Не запускался</span>`;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3385
3442
|
function renderProbePanel(c) {
|
|
3443
|
+
if (probeDetailCheck !== null) { renderProbeDetail(c); return; }
|
|
3444
|
+
|
|
3386
3445
|
const d = probeData;
|
|
3387
|
-
const
|
|
3388
|
-
const
|
|
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>`;
|
|
3446
|
+
const isRunning = d?.status === 'running';
|
|
3447
|
+
const isScheduled = d?.status === 'scheduled';
|
|
3411
3448
|
|
|
3412
3449
|
const intervalOptions = [60, 300, 600, 1800, 3600].map(s => {
|
|
3413
3450
|
const label = s < 3600 ? `каждые ${s / 60} мин` : `каждый час`;
|
|
3414
|
-
const sel = d
|
|
3451
|
+
const sel = d?.intervalSec === s ? ' selected' : '';
|
|
3415
3452
|
return `<option value="${s}"${sel}>${label}</option>`;
|
|
3416
3453
|
}).join('');
|
|
3417
3454
|
|
|
3418
|
-
const
|
|
3419
|
-
|
|
3455
|
+
const checks = d?.checks || [];
|
|
3456
|
+
|
|
3457
|
+
const cards = checks.map(ch => {
|
|
3458
|
+
const status = probeCheckStatus(ch.name);
|
|
3459
|
+
const result = probeCheckResult(ch.name);
|
|
3460
|
+
const dur = result ? `${(result.durationMs / 1000).toFixed(1)}s` : '';
|
|
3461
|
+
const typeLabel = ch.type === 'file' ? 'playwright' : 'dsl';
|
|
3462
|
+
const stepsCount = ch.type === 'dsl' ? `${ch.steps.length} шагов` : ch.file?.split('/').pop() || '';
|
|
3463
|
+
return `
|
|
3464
|
+
<div class="probe-card ${status}" onclick="probeOpenDetail(${JSON.stringify(ch.name)})">
|
|
3465
|
+
<div class="probe-card-top">
|
|
3466
|
+
<div class="probe-card-name">${escapeHtml(ch.name)}</div>
|
|
3467
|
+
<div class="probe-card-icon">${probeStatusIcon(status)}</div>
|
|
3468
|
+
</div>
|
|
3469
|
+
<div class="probe-card-meta">
|
|
3470
|
+
<span class="probe-badge">${typeLabel}</span>
|
|
3471
|
+
<span class="probe-card-dur" style="color:var(--muted);font-size:11px">${escapeHtml(stepsCount)}</span>
|
|
3472
|
+
${dur ? `<span class="probe-card-dur">${dur}</span>` : ''}
|
|
3473
|
+
</div>
|
|
3474
|
+
${result?.error ? `<div class="probe-card-err">${escapeHtml(result.error)}</div>` : ''}
|
|
3475
|
+
<div class="probe-card-footer">
|
|
3476
|
+
<button class="probe-btn probe-btn-run" style="font-size:11px;padding:4px 10px"
|
|
3477
|
+
onclick="event.stopPropagation();probeRunOne(${JSON.stringify(ch.name)})"
|
|
3478
|
+
${status === 'running' ? 'disabled' : ''}>▶ Run</button>
|
|
3479
|
+
</div>
|
|
3480
|
+
</div>`;
|
|
3481
|
+
}).join('');
|
|
3482
|
+
|
|
3483
|
+
const lastTs = d?.lastRun?.timestamp ? `· последний запуск ${new Date(d.lastRun.timestamp).toLocaleTimeString()}` : '';
|
|
3484
|
+
const passedCount = checks.filter(ch => probeCheckStatus(ch.name) === 'passed').length;
|
|
3485
|
+
const failedCount = checks.filter(ch => probeCheckStatus(ch.name) === 'failed').length;
|
|
3486
|
+
const summary = checks.length
|
|
3487
|
+
? `${failedCount > 0 ? `<span style="color:var(--red)">❌ ${failedCount}</span> · ` : ''}${passedCount > 0 ? `<span style="color:var(--green)">✅ ${passedCount}</span> · ` : ''}${checks.length} checks ${lastTs}`
|
|
3488
|
+
: '';
|
|
3420
3489
|
|
|
3421
3490
|
c.innerHTML = `
|
|
3422
3491
|
<div class="probe-container">
|
|
3423
3492
|
<div class="probe-header">
|
|
3424
3493
|
<div>
|
|
3425
3494
|
<div class="probe-title">🔭 Probe monitoring</div>
|
|
3426
|
-
|
|
3427
|
-
:
|
|
3495
|
+
<div style="font-size:12px;color:var(--muted);margin-top:3px">
|
|
3496
|
+
${d?.effectiveTarget ? escapeHtml(d.effectiveTarget) + ' · ' : ''}${summary || '<span style="color:#e3b341">⚠️ Задайте домен в Настройках</span>'}
|
|
3497
|
+
</div>
|
|
3428
3498
|
</div>
|
|
3429
3499
|
<button class="probe-btn" onclick="openProbeSettingsModal()">⚙️ Настройки</button>
|
|
3430
3500
|
</div>
|
|
3431
3501
|
|
|
3432
|
-
<div class="probe-section">
|
|
3502
|
+
<div class="probe-section" style="margin-bottom:0">
|
|
3433
3503
|
<div class="probe-controls">
|
|
3434
3504
|
<button class="probe-btn probe-btn-run" onclick="probeRunNow()" ${isRunning ? 'disabled' : ''}>
|
|
3435
|
-
${isRunning ? '
|
|
3505
|
+
${isRunning ? '<span class="probe-spin">⏳</span> Выполняется…' : '▶ Run All'}
|
|
3436
3506
|
</button>
|
|
3437
3507
|
<div class="probe-schedule-row">
|
|
3438
|
-
<select id="probeIntervalSelect" class="probe-select">
|
|
3439
|
-
${intervalOptions}
|
|
3440
|
-
</select>
|
|
3508
|
+
<select id="probeIntervalSelect" class="probe-select">${intervalOptions}</select>
|
|
3441
3509
|
${isScheduled
|
|
3442
3510
|
? `<button class="probe-btn probe-btn-stop" onclick="probeScheduleStop()">⏹ Стоп</button>`
|
|
3443
|
-
: `<button class="probe-btn probe-btn-start" onclick="probeScheduleStart()">🕐 По расписанию</button>`
|
|
3444
|
-
|
|
3511
|
+
: `<button class="probe-btn probe-btn-start" onclick="probeScheduleStart()">🕐 По расписанию</button>`}
|
|
3512
|
+
</div>
|
|
3513
|
+
${isScheduled && d.nextRunAt ? `<span style="font-size:11px;color:var(--muted)">след. ${new Date(d.nextRunAt).toLocaleTimeString()}</span>` : ''}
|
|
3514
|
+
</div>
|
|
3515
|
+
</div>
|
|
3516
|
+
|
|
3517
|
+
${checks.length
|
|
3518
|
+
? `<div class="probe-cards">${cards}</div>`
|
|
3519
|
+
: `<div style="color:var(--muted);font-size:13px;padding:24px 0">Нет проверок в probe.config.yml</div>`}
|
|
3520
|
+
</div>`;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
function probeOpenDetail(checkName) {
|
|
3524
|
+
probeDetailCheck = checkName;
|
|
3525
|
+
renderContent();
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
function renderProbeDetail(c) {
|
|
3529
|
+
const d = probeData;
|
|
3530
|
+
const checks = d?.checks || [];
|
|
3531
|
+
const ch = checks.find(x => x.name === probeDetailCheck);
|
|
3532
|
+
if (!ch) { probeDetailCheck = null; renderProbePanel(c); return; }
|
|
3533
|
+
|
|
3534
|
+
const status = probeCheckStatus(ch.name);
|
|
3535
|
+
const result = probeCheckResult(ch.name);
|
|
3536
|
+
const isRunning = status === 'running';
|
|
3537
|
+
|
|
3538
|
+
const stepsHtml = ch.type === 'dsl' && ch.steps?.length
|
|
3539
|
+
? `<div style="font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Шаги</div>
|
|
3540
|
+
<div class="probe-steps-list">
|
|
3541
|
+
${ch.steps.map(s => `
|
|
3542
|
+
<div class="probe-step-row">
|
|
3543
|
+
<span class="probe-step-type">${escapeHtml(s.type)}</span>
|
|
3544
|
+
<span class="probe-step-val">${escapeHtml(s.value)}</span>
|
|
3545
|
+
</div>`).join('')}
|
|
3546
|
+
</div>` : '';
|
|
3547
|
+
|
|
3548
|
+
const fileHtml = ch.type === 'file'
|
|
3549
|
+
? `<div style="font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px">Файл теста</div>
|
|
3550
|
+
<code style="font-size:12px;color:var(--blue);background:var(--bg);padding:6px 10px;border-radius:4px;display:block">${escapeHtml(ch.file || '')}</code>` : '';
|
|
3551
|
+
|
|
3552
|
+
const outputHtml = result?.output
|
|
3553
|
+
? `<div style="font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-top:20px;margin-bottom:6px">Вывод</div>
|
|
3554
|
+
<div class="probe-output-box">${escapeHtml(result.output)}</div>` : '';
|
|
3555
|
+
|
|
3556
|
+
const errorHtml = result?.error && !result.output
|
|
3557
|
+
? `<div style="font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-top:20px;margin-bottom:6px">Ошибка</div>
|
|
3558
|
+
<div class="probe-output-box" style="color:var(--red)">${escapeHtml(result.error)}</div>` : '';
|
|
3559
|
+
|
|
3560
|
+
c.innerHTML = `
|
|
3561
|
+
<div class="probe-detail">
|
|
3562
|
+
<div class="probe-back" onclick="probeDetailCheck=null;renderContent()">← Назад к проверкам</div>
|
|
3563
|
+
|
|
3564
|
+
<div class="probe-detail-header">
|
|
3565
|
+
<div>
|
|
3566
|
+
<div class="probe-detail-title">${escapeHtml(ch.name)}</div>
|
|
3567
|
+
<div class="probe-detail-meta">
|
|
3568
|
+
<span class="probe-badge">${ch.type === 'file' ? 'playwright' : 'dsl'}</span>
|
|
3569
|
+
${probeStatusBadge(status)}
|
|
3570
|
+
${result ? `<span style="color:var(--muted)">${(result.durationMs / 1000).toFixed(1)}s</span>` : ''}
|
|
3571
|
+
${result ? `<span style="color:var(--muted)">${new Date().toLocaleTimeString()}</span>` : ''}
|
|
3445
3572
|
</div>
|
|
3446
3573
|
</div>
|
|
3447
|
-
|
|
3574
|
+
<button class="probe-btn probe-btn-run" style="flex-shrink:0" onclick="probeRunOne(${JSON.stringify(ch.name)})" ${isRunning ? 'disabled' : ''}>
|
|
3575
|
+
${isRunning ? '<span class="probe-spin">⏳</span> Выполняется…' : '▶ Run'}
|
|
3576
|
+
</button>
|
|
3448
3577
|
</div>
|
|
3449
3578
|
|
|
3450
|
-
${
|
|
3579
|
+
${stepsHtml}
|
|
3580
|
+
${fileHtml}
|
|
3581
|
+
${outputHtml}
|
|
3582
|
+
${errorHtml}
|
|
3451
3583
|
</div>`;
|
|
3452
3584
|
}
|
|
3453
3585
|
|
|
3454
3586
|
async function probeRunNow() {
|
|
3455
3587
|
probeRunning = true;
|
|
3456
3588
|
renderContent();
|
|
3457
|
-
try { await fetch('/api/probe/run', { method: 'POST' }); } catch {}
|
|
3589
|
+
try { await fetch('/api/probe/run', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' }); } catch {}
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
async function probeRunOne(checkName) {
|
|
3593
|
+
try {
|
|
3594
|
+
await fetch('/api/probe/run', {
|
|
3595
|
+
method: 'POST',
|
|
3596
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3597
|
+
body: JSON.stringify({ checkName }),
|
|
3598
|
+
});
|
|
3599
|
+
renderContent();
|
|
3600
|
+
} catch {}
|
|
3458
3601
|
}
|
|
3459
3602
|
|
|
3460
3603
|
async function probeScheduleStart() {
|
|
@@ -8004,11 +8147,29 @@ function connectSSE() {
|
|
|
8004
8147
|
if (contextMode === 'load') { renderSidebar(); renderContent(); }
|
|
8005
8148
|
});
|
|
8006
8149
|
|
|
8007
|
-
es.addEventListener('probe-run-started', () => {
|
|
8150
|
+
es.addEventListener('probe-run-started', (e) => {
|
|
8151
|
+
const payload = JSON.parse(e.data || '{}');
|
|
8008
8152
|
if (!probeData) probeData = {};
|
|
8009
8153
|
probeData.status = 'running';
|
|
8154
|
+
probeData.runningCheck = null;
|
|
8010
8155
|
probeRunning = true;
|
|
8011
|
-
if (
|
|
8156
|
+
if (contextMode === 'probe') renderContent();
|
|
8157
|
+
});
|
|
8158
|
+
|
|
8159
|
+
es.addEventListener('probe-check-started', (e) => {
|
|
8160
|
+
const payload = JSON.parse(e.data || '{}');
|
|
8161
|
+
if (!probeData) probeData = {};
|
|
8162
|
+
probeData.runningCheck = payload.checkName;
|
|
8163
|
+
if (contextMode === 'probe') renderContent();
|
|
8164
|
+
});
|
|
8165
|
+
|
|
8166
|
+
es.addEventListener('probe-check-done', (e) => {
|
|
8167
|
+
const result = JSON.parse(e.data || '{}');
|
|
8168
|
+
if (!probeData) probeData = {};
|
|
8169
|
+
if (!probeData.checkResults) probeData.checkResults = {};
|
|
8170
|
+
probeData.checkResults[result.check] = result;
|
|
8171
|
+
probeData.runningCheck = null;
|
|
8172
|
+
if (contextMode === 'probe') renderContent();
|
|
8012
8173
|
});
|
|
8013
8174
|
|
|
8014
8175
|
es.addEventListener('probe-run-done', (e) => {
|
|
@@ -8016,8 +8177,9 @@ function connectSSE() {
|
|
|
8016
8177
|
if (!probeData) probeData = {};
|
|
8017
8178
|
if (payload.results) probeData.lastRun = payload;
|
|
8018
8179
|
probeData.status = probeData.intervalSec ? 'scheduled' : 'idle';
|
|
8180
|
+
probeData.runningCheck = null;
|
|
8019
8181
|
probeRunning = false;
|
|
8020
|
-
if (
|
|
8182
|
+
if (contextMode === 'probe') renderContent();
|
|
8021
8183
|
});
|
|
8022
8184
|
|
|
8023
8185
|
es.addEventListener('probe-scheduled', (e) => {
|
|
@@ -8026,7 +8188,7 @@ function connectSSE() {
|
|
|
8026
8188
|
probeData.status = payload.status;
|
|
8027
8189
|
probeData.intervalSec = payload.intervalSec;
|
|
8028
8190
|
probeData.nextRunAt = payload.nextRunAt;
|
|
8029
|
-
if (
|
|
8191
|
+
if (contextMode === 'probe') renderContent();
|
|
8030
8192
|
});
|
|
8031
8193
|
|
|
8032
8194
|
es.onerror = () => {
|