viberadar 0.3.230 → 0.3.232
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/scanner/index.d.ts +2 -0
- package/dist/scanner/index.d.ts.map +1 -1
- package/dist/scanner/index.js +18 -4
- package/dist/scanner/index.js.map +1 -1
- package/dist/scanner/microservices.d.ts +84 -0
- package/dist/scanner/microservices.d.ts.map +1 -0
- package/dist/scanner/microservices.js +360 -0
- package/dist/scanner/microservices.js.map +1 -0
- package/dist/ui/dashboard.html +497 -34
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -330,8 +330,116 @@
|
|
|
330
330
|
.obs-title { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; margin-bottom: 10px; }
|
|
331
331
|
.obs-value { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
|
|
332
332
|
.obs-sub { font-size: 12px; color: var(--muted); line-height: 1.4; }
|
|
333
|
-
.obs-list { display: flex; flex-direction: column; gap: 6px; }
|
|
334
|
-
.obs-list-item { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
|
|
333
|
+
.obs-list { display: flex; flex-direction: column; gap: 6px; }
|
|
334
|
+
.obs-list-item { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
|
|
335
|
+
|
|
336
|
+
/* ── Microservices readiness ────────────────────────────────────────────── */
|
|
337
|
+
.micro-summary-grid {
|
|
338
|
+
display: grid;
|
|
339
|
+
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
|
340
|
+
gap: 12px;
|
|
341
|
+
margin-bottom: 14px;
|
|
342
|
+
}
|
|
343
|
+
.micro-panel {
|
|
344
|
+
background: var(--bg-card);
|
|
345
|
+
border: 1px solid var(--border);
|
|
346
|
+
border-radius: 8px;
|
|
347
|
+
padding: 14px;
|
|
348
|
+
}
|
|
349
|
+
.micro-panel-title {
|
|
350
|
+
font-size: 12px;
|
|
351
|
+
color: var(--muted);
|
|
352
|
+
text-transform: uppercase;
|
|
353
|
+
letter-spacing: 0.4px;
|
|
354
|
+
margin-bottom: 10px;
|
|
355
|
+
}
|
|
356
|
+
.micro-risk-list { display: flex; flex-direction: column; gap: 8px; }
|
|
357
|
+
.micro-risk-item {
|
|
358
|
+
display: grid;
|
|
359
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
360
|
+
gap: 8px;
|
|
361
|
+
align-items: center;
|
|
362
|
+
font-size: 12px;
|
|
363
|
+
color: var(--muted);
|
|
364
|
+
padding: 7px 0;
|
|
365
|
+
border-bottom: 1px dashed var(--border);
|
|
366
|
+
}
|
|
367
|
+
.micro-risk-item:last-child { border-bottom: none; }
|
|
368
|
+
.micro-path { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
|
|
369
|
+
.micro-pill-row { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
370
|
+
.micro-pill {
|
|
371
|
+
border: 1px solid var(--border);
|
|
372
|
+
border-radius: 999px;
|
|
373
|
+
padding: 1px 7px;
|
|
374
|
+
font-size: 10px;
|
|
375
|
+
color: var(--muted);
|
|
376
|
+
background: var(--bg);
|
|
377
|
+
white-space: nowrap;
|
|
378
|
+
}
|
|
379
|
+
.micro-pill.high { color: var(--red); border-color: rgba(248,81,73,0.45); }
|
|
380
|
+
.micro-pill.medium { color: var(--yellow); border-color: rgba(227,179,65,0.45); }
|
|
381
|
+
.micro-pill.ready { color: var(--green); border-color: rgba(63,185,80,0.45); }
|
|
382
|
+
.micro-pill.watch { color: var(--yellow); border-color: rgba(227,179,65,0.45); }
|
|
383
|
+
.micro-pill.risky { color: var(--red); border-color: rgba(248,81,73,0.45); }
|
|
384
|
+
.micro-score {
|
|
385
|
+
font-size: 22px;
|
|
386
|
+
font-weight: 700;
|
|
387
|
+
color: var(--blue);
|
|
388
|
+
margin-bottom: 4px;
|
|
389
|
+
}
|
|
390
|
+
.micro-score-bar {
|
|
391
|
+
height: 6px;
|
|
392
|
+
background: var(--border);
|
|
393
|
+
border-radius: 999px;
|
|
394
|
+
overflow: hidden;
|
|
395
|
+
margin-top: 8px;
|
|
396
|
+
}
|
|
397
|
+
.micro-score-fill { height: 100%; background: var(--blue); border-radius: 999px; }
|
|
398
|
+
.micro-table {
|
|
399
|
+
width: 100%;
|
|
400
|
+
border-collapse: collapse;
|
|
401
|
+
font-size: 12px;
|
|
402
|
+
background: var(--bg-card);
|
|
403
|
+
border: 1px solid var(--border);
|
|
404
|
+
border-radius: 8px;
|
|
405
|
+
overflow: hidden;
|
|
406
|
+
}
|
|
407
|
+
.micro-table th,
|
|
408
|
+
.micro-table td {
|
|
409
|
+
text-align: left;
|
|
410
|
+
padding: 9px 10px;
|
|
411
|
+
border-bottom: 1px solid var(--border);
|
|
412
|
+
vertical-align: top;
|
|
413
|
+
}
|
|
414
|
+
.micro-table th {
|
|
415
|
+
color: var(--muted);
|
|
416
|
+
text-transform: uppercase;
|
|
417
|
+
font-size: 10px;
|
|
418
|
+
letter-spacing: 0.4px;
|
|
419
|
+
background: var(--bg);
|
|
420
|
+
}
|
|
421
|
+
.micro-table tr:last-child td { border-bottom: none; }
|
|
422
|
+
.micro-table tr.clickable { cursor: pointer; }
|
|
423
|
+
.micro-table tr.clickable:hover { background: var(--bg-hover); }
|
|
424
|
+
.micro-section-head {
|
|
425
|
+
display: flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
justify-content: space-between;
|
|
428
|
+
gap: 10px;
|
|
429
|
+
margin: 18px 0 10px;
|
|
430
|
+
}
|
|
431
|
+
.micro-section-head h3 { font-size: 16px; }
|
|
432
|
+
.micro-subtle { font-size: 12px; color: var(--muted); line-height: 1.45; }
|
|
433
|
+
.micro-back {
|
|
434
|
+
border: 1px solid var(--border);
|
|
435
|
+
background: var(--bg-card);
|
|
436
|
+
color: var(--text);
|
|
437
|
+
border-radius: 6px;
|
|
438
|
+
padding: 6px 10px;
|
|
439
|
+
cursor: pointer;
|
|
440
|
+
font-size: 12px;
|
|
441
|
+
}
|
|
442
|
+
.micro-back:hover { background: var(--bg-hover); border-color: var(--blue); }
|
|
335
443
|
|
|
336
444
|
/* ── Feature cards ───────────────────────────────────────────────────────── */
|
|
337
445
|
.features-grid {
|
|
@@ -1991,6 +2099,12 @@ let taskImportDraft = '';
|
|
|
1991
2099
|
let taskImportResult = '';
|
|
1992
2100
|
let taskSelectedId = null;
|
|
1993
2101
|
let taskEditMode = false;
|
|
2102
|
+
let microFeatureFilter = 'all';
|
|
2103
|
+
let microTierFilter = 'all';
|
|
2104
|
+
let microSizeFilter = 'problem';
|
|
2105
|
+
let microTypeFilter = 'all';
|
|
2106
|
+
let microSearchQuery = '';
|
|
2107
|
+
let microDrillKey = null;
|
|
1994
2108
|
|
|
1995
2109
|
function toggleObsHint(id) {
|
|
1996
2110
|
document.getElementById(id).classList.toggle('open');
|
|
@@ -2007,8 +2121,9 @@ function switchObsTab(tabId) {
|
|
|
2007
2121
|
}
|
|
2008
2122
|
const modeStore = {
|
|
2009
2123
|
qa: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
2010
|
-
observability: { view: 'files', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
2011
|
-
|
|
2124
|
+
observability: { view: 'files', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
2125
|
+
microservices: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
2126
|
+
docs: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
2012
2127
|
scenarios: { view: 'list', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
2013
2128
|
services: { view: 'graph', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null, svcTab: 'graph' },
|
|
2014
2129
|
load: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false, testNavType: 'all', testNavFeature: null, testNavProblem: null },
|
|
@@ -2018,6 +2133,7 @@ const modeStore = {
|
|
|
2018
2133
|
|
|
2019
2134
|
function getModeFromPath(pathname = window.location.pathname) {
|
|
2020
2135
|
if (pathname.startsWith('/radar/observability')) return 'observability';
|
|
2136
|
+
if (pathname.startsWith('/radar/microservices')) return 'microservices';
|
|
2021
2137
|
if (pathname.startsWith('/radar/docs')) return 'docs';
|
|
2022
2138
|
if (pathname.startsWith('/radar/services')) return 'services';
|
|
2023
2139
|
if (pathname.startsWith('/radar/load')) return 'load';
|
|
@@ -2028,6 +2144,7 @@ function getModeFromPath(pathname = window.location.pathname) {
|
|
|
2028
2144
|
|
|
2029
2145
|
function routePathForMode(mode) {
|
|
2030
2146
|
if (mode === 'observability') return '/radar/observability';
|
|
2147
|
+
if (mode === 'microservices') return '/radar/microservices';
|
|
2031
2148
|
if (mode === 'docs') return '/radar/docs';
|
|
2032
2149
|
if (mode === 'services') return '/radar/services';
|
|
2033
2150
|
if (mode === 'load') return '/radar/load';
|
|
@@ -2076,7 +2193,7 @@ function switchMode(nextMode) {
|
|
|
2076
2193
|
saveModeState(contextMode);
|
|
2077
2194
|
contextMode = nextMode;
|
|
2078
2195
|
restoreModeState(contextMode);
|
|
2079
|
-
if (contextMode === 'observability' || contextMode === 'docs' || contextMode === 'services' || contextMode === 'load' || contextMode === 'probe' || contextMode === 'tasks') {
|
|
2196
|
+
if (contextMode === 'observability' || contextMode === 'microservices' || contextMode === 'docs' || contextMode === 'services' || contextMode === 'load' || contextMode === 'probe' || contextMode === 'tasks') {
|
|
2080
2197
|
view = 'features';
|
|
2081
2198
|
drillFeatureKey = null;
|
|
2082
2199
|
drillTestType = null;
|
|
@@ -3393,8 +3510,8 @@ function renderStats() {
|
|
|
3393
3510
|
const pct = src.length ? Math.round(tested / src.length * 100) : 0;
|
|
3394
3511
|
|
|
3395
3512
|
let items;
|
|
3396
|
-
if (contextMode === 'observability') {
|
|
3397
|
-
const o = D.observability;
|
|
3513
|
+
if (contextMode === 'observability') {
|
|
3514
|
+
const o = D.observability;
|
|
3398
3515
|
if (o) {
|
|
3399
3516
|
const noiseRatio = Math.round(o.metrics.noise_ratio * 100);
|
|
3400
3517
|
const structPct = Math.round(o.metrics.structured_completeness * 100);
|
|
@@ -3417,8 +3534,25 @@ function renderStats() {
|
|
|
3417
3534
|
{ v: '—', l: 'Структурированность' },
|
|
3418
3535
|
{ v: '—', l: 'Нет покрытия' },
|
|
3419
3536
|
{ v: '—', l: 'Шумных паттернов' },
|
|
3420
|
-
];
|
|
3421
|
-
}
|
|
3537
|
+
];
|
|
3538
|
+
}
|
|
3539
|
+
} else if (contextMode === 'microservices') {
|
|
3540
|
+
const ms = D.microservices;
|
|
3541
|
+
if (ms) {
|
|
3542
|
+
items = [
|
|
3543
|
+
{ v: ms.summary.godFiles + ms.summary.criticalFiles, l: 'God files', c: (ms.summary.godFiles + ms.summary.criticalFiles) ? '#f85149' : '#3fb950' },
|
|
3544
|
+
{ v: ms.summary.largeFiles, l: 'Large files', c: ms.summary.largeFiles ? '#e3b341' : undefined },
|
|
3545
|
+
{ v: ms.summary.modules, l: 'Modules' },
|
|
3546
|
+
{ v: ms.summary.readyModules, l: 'Ready >= 70', c: '#3fb950' },
|
|
3547
|
+
{ v: ms.summary.riskyModules, l: 'Risky < 40', c: ms.summary.riskyModules ? '#f85149' : undefined },
|
|
3548
|
+
];
|
|
3549
|
+
} else {
|
|
3550
|
+
items = [
|
|
3551
|
+
{ v: '—', l: 'God files' },
|
|
3552
|
+
{ v: '—', l: 'Large files' },
|
|
3553
|
+
{ v: '—', l: 'Modules' },
|
|
3554
|
+
];
|
|
3555
|
+
}
|
|
3422
3556
|
} else if (contextMode === 'tasks') {
|
|
3423
3557
|
const tasks = taskTracker.tasks || [];
|
|
3424
3558
|
const active = tasks.filter(t => t.status !== 'archived');
|
|
@@ -3478,10 +3612,11 @@ function renderStats() {
|
|
|
3478
3612
|
|
|
3479
3613
|
function renderModeSwitch() {
|
|
3480
3614
|
const root = document.getElementById('modeSwitch');
|
|
3481
|
-
const modes = [
|
|
3482
|
-
{ key: 'qa', label: 'QA Coverage', hint: 'Покрытие, пробелы, тренды' },
|
|
3483
|
-
{ key: 'observability', label: 'Наблюдаемость', hint: 'Логи, шум, сигналы ошибок' },
|
|
3484
|
-
{ key: '
|
|
3615
|
+
const modes = [
|
|
3616
|
+
{ key: 'qa', label: 'QA Coverage', hint: 'Покрытие, пробелы, тренды' },
|
|
3617
|
+
{ key: 'observability', label: 'Наблюдаемость', hint: 'Логи, шум, сигналы ошибок' },
|
|
3618
|
+
{ key: 'microservices', label: 'Микросервисы', hint: 'God files, границы, готовность' },
|
|
3619
|
+
{ key: 'docs', label: 'Документация', hint: 'Актуальность, генерация, обновление' },
|
|
3485
3620
|
{ key: 'scenarios', label: 'Сценарии', hint: 'Пользовательские сценарии, user journeys' },
|
|
3486
3621
|
{ key: 'services', label: 'Карта сервисов', hint: 'Зависимости, пайплайны, мониторинг' },
|
|
3487
3622
|
{ key: 'load', label: 'Нагрузка', hint: 'k6: метрики, сценарии, AI-анализ' },
|
|
@@ -3515,17 +3650,71 @@ function renderSidebar() {
|
|
|
3515
3650
|
return;
|
|
3516
3651
|
}
|
|
3517
3652
|
|
|
3518
|
-
if (contextMode === 'observability') {
|
|
3519
|
-
tabs.style.display = 'none';
|
|
3520
|
-
extra.innerHTML = `
|
|
3521
|
-
<div class="sidebar-label">Фокус наблюдаемости</div>
|
|
3522
|
-
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45">
|
|
3523
|
-
Источники логов и качество сигналов для триажа инцидентов.
|
|
3524
|
-
</div>`;
|
|
3525
|
-
return;
|
|
3526
|
-
}
|
|
3527
|
-
|
|
3528
|
-
if (contextMode === '
|
|
3653
|
+
if (contextMode === 'observability') {
|
|
3654
|
+
tabs.style.display = 'none';
|
|
3655
|
+
extra.innerHTML = `
|
|
3656
|
+
<div class="sidebar-label">Фокус наблюдаемости</div>
|
|
3657
|
+
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45">
|
|
3658
|
+
Источники логов и качество сигналов для триажа инцидентов.
|
|
3659
|
+
</div>`;
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
if (contextMode === 'microservices') {
|
|
3664
|
+
tabs.style.display = 'none';
|
|
3665
|
+
const ms = D.microservices;
|
|
3666
|
+
const features = D.features || [];
|
|
3667
|
+
const types = ms ? Array.from(new Set(ms.files.map(f => f.type))).sort() : [];
|
|
3668
|
+
extra.innerHTML = `
|
|
3669
|
+
<div class="sidebar-label">Микросервисы</div>
|
|
3670
|
+
<div style="font-size:12px;color:var(--muted);padding:0 6px;line-height:1.45;margin-bottom:12px">
|
|
3671
|
+
Наблюдение за размером файлов, границами фич и готовностью к сервисному выделению.
|
|
3672
|
+
</div>
|
|
3673
|
+
<div style="padding:0 6px;display:flex;flex-direction:column;gap:8px">
|
|
3674
|
+
<label class="sidebar-label" style="padding:0">Поиск</label>
|
|
3675
|
+
<input id="microSearchInput" class="search-input" placeholder="Файл или модуль" value="${escapeHtml(microSearchQuery)}">
|
|
3676
|
+
<label class="sidebar-label" style="padding:0">Фича</label>
|
|
3677
|
+
<select id="microFeatureFilter" class="search-input">
|
|
3678
|
+
<option value="all" ${microFeatureFilter === 'all' ? 'selected' : ''}>Все фичи</option>
|
|
3679
|
+
<option value="__unknown__" ${microFeatureFilter === '__unknown__' ? 'selected' : ''}>Unknown ownership</option>
|
|
3680
|
+
${features.map(f => `<option value="${escapeHtml(f.key)}" ${microFeatureFilter === f.key ? 'selected' : ''}>${escapeHtml(f.label)}</option>`).join('')}
|
|
3681
|
+
</select>
|
|
3682
|
+
<label class="sidebar-label" style="padding:0">Готовность</label>
|
|
3683
|
+
<select id="microTierFilter" class="search-input">
|
|
3684
|
+
<option value="all" ${microTierFilter === 'all' ? 'selected' : ''}>Любая</option>
|
|
3685
|
+
<option value="ready" ${microTierFilter === 'ready' ? 'selected' : ''}>Ready >= 70</option>
|
|
3686
|
+
<option value="watch" ${microTierFilter === 'watch' ? 'selected' : ''}>Watch 40-69</option>
|
|
3687
|
+
<option value="risky" ${microTierFilter === 'risky' ? 'selected' : ''}>Risky < 40</option>
|
|
3688
|
+
</select>
|
|
3689
|
+
<label class="sidebar-label" style="padding:0">Размер</label>
|
|
3690
|
+
<select id="microSizeFilter" class="search-input">
|
|
3691
|
+
<option value="problem" ${microSizeFilter === 'problem' ? 'selected' : ''}>Large + god + critical</option>
|
|
3692
|
+
<option value="all" ${microSizeFilter === 'all' ? 'selected' : ''}>Все</option>
|
|
3693
|
+
<option value="critical" ${microSizeFilter === 'critical' ? 'selected' : ''}>Critical</option>
|
|
3694
|
+
<option value="god" ${microSizeFilter === 'god' ? 'selected' : ''}>God</option>
|
|
3695
|
+
<option value="large" ${microSizeFilter === 'large' ? 'selected' : ''}>Large</option>
|
|
3696
|
+
<option value="normal" ${microSizeFilter === 'normal' ? 'selected' : ''}>Normal</option>
|
|
3697
|
+
</select>
|
|
3698
|
+
<label class="sidebar-label" style="padding:0">Тип файла</label>
|
|
3699
|
+
<select id="microTypeFilter" class="search-input">
|
|
3700
|
+
<option value="all" ${microTypeFilter === 'all' ? 'selected' : ''}>Все типы</option>
|
|
3701
|
+
${types.map(t => `<option value="${escapeHtml(t)}" ${microTypeFilter === t ? 'selected' : ''}>${escapeHtml(t)}</option>`).join('')}
|
|
3702
|
+
</select>
|
|
3703
|
+
${ms ? `<div class="micro-subtle" style="margin-top:6px">
|
|
3704
|
+
Source: <span style="color:var(--text)">${ms.summary.sourceFiles}</span><br>
|
|
3705
|
+
Unknown: <span style="color:var(--text)">${ms.summary.unmappedFiles}</span><br>
|
|
3706
|
+
Multi-feature: <span style="color:var(--text)">${ms.summary.multiFeatureFiles}</span>
|
|
3707
|
+
</div>` : ''}
|
|
3708
|
+
</div>`;
|
|
3709
|
+
document.getElementById('microSearchInput').oninput = (e) => { microSearchQuery = e.target.value; renderContent(); };
|
|
3710
|
+
document.getElementById('microFeatureFilter').onchange = (e) => { microFeatureFilter = e.target.value; microDrillKey = null; renderStats(); renderContent(); };
|
|
3711
|
+
document.getElementById('microTierFilter').onchange = (e) => { microTierFilter = e.target.value; microDrillKey = null; renderStats(); renderContent(); };
|
|
3712
|
+
document.getElementById('microSizeFilter').onchange = (e) => { microSizeFilter = e.target.value; renderContent(); };
|
|
3713
|
+
document.getElementById('microTypeFilter').onchange = (e) => { microTypeFilter = e.target.value; renderContent(); };
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
if (contextMode === 'docs') {
|
|
3529
3718
|
tabs.style.display = 'none';
|
|
3530
3719
|
extra.innerHTML = `
|
|
3531
3720
|
<div class="sidebar-label">Документация</div>
|
|
@@ -3690,17 +3879,288 @@ function renderSidebar() {
|
|
|
3690
3879
|
renderSidebar();
|
|
3691
3880
|
renderContent();
|
|
3692
3881
|
};
|
|
3693
|
-
});
|
|
3694
|
-
}
|
|
3695
|
-
|
|
3696
|
-
// ───
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
// ─── Microservices readiness ─────────────────────────────────────────────────
|
|
3886
|
+
function microCategoryLabel(cat) {
|
|
3887
|
+
return cat === 'critical' ? 'Critical' : cat === 'god' ? 'God' : cat === 'large' ? 'Large' : 'Normal';
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
function microTierLabel(tier) {
|
|
3891
|
+
return tier === 'ready' ? 'Ready' : tier === 'risky' ? 'Risky' : 'Watch';
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
function microScoreColor(score) {
|
|
3895
|
+
if (score >= 70) return 'var(--green)';
|
|
3896
|
+
if (score < 40) return 'var(--red)';
|
|
3897
|
+
return 'var(--yellow)';
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
function microRiskPills(risks, max = 3) {
|
|
3901
|
+
const shown = (risks || []).slice(0, max);
|
|
3902
|
+
const rest = Math.max(0, (risks || []).length - shown.length);
|
|
3903
|
+
return `
|
|
3904
|
+
<div class="micro-pill-row">
|
|
3905
|
+
${shown.map(r => `<span class="micro-pill ${r.severity}">${escapeHtml(r.label)}</span>`).join('')}
|
|
3906
|
+
${rest ? `<span class="micro-pill">+${rest}</span>` : ''}
|
|
3907
|
+
</div>`;
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
function microFeatureLabels(keys) {
|
|
3911
|
+
if (!keys || keys.length === 0) return '<span class="micro-pill medium">unknown</span>';
|
|
3912
|
+
const byKey = new Map((D.features || []).map(f => [f.key, f.label]));
|
|
3913
|
+
return `<div class="micro-pill-row">${keys.map(k => `<span class="micro-pill">${escapeHtml(byKey.get(k) || k)}</span>`).join('')}</div>`;
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
function filteredMicroModules(ms) {
|
|
3917
|
+
const q = microSearchQuery.trim().toLowerCase();
|
|
3918
|
+
return (ms.modules || []).filter(m => {
|
|
3919
|
+
if (microFeatureFilter !== 'all' && m.key !== microFeatureFilter) return false;
|
|
3920
|
+
if (microTierFilter !== 'all' && m.readinessTier !== microTierFilter) return false;
|
|
3921
|
+
if (q && !(`${m.key} ${m.label} ${m.description}`.toLowerCase().includes(q))) return false;
|
|
3922
|
+
return true;
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
function filteredMicroFiles(ms, moduleKey = null) {
|
|
3927
|
+
const q = microSearchQuery.trim().toLowerCase();
|
|
3928
|
+
return (ms.files || []).filter(f => {
|
|
3929
|
+
if (moduleKey) {
|
|
3930
|
+
if (moduleKey === '__unknown__') {
|
|
3931
|
+
if (f.featureKeys.length !== 0) return false;
|
|
3932
|
+
} else if (!f.featureKeys.includes(moduleKey)) return false;
|
|
3933
|
+
} else if (microFeatureFilter !== 'all') {
|
|
3934
|
+
if (microFeatureFilter === '__unknown__') {
|
|
3935
|
+
if (f.featureKeys.length !== 0) return false;
|
|
3936
|
+
} else if (!f.featureKeys.includes(microFeatureFilter)) return false;
|
|
3937
|
+
}
|
|
3938
|
+
if (microSizeFilter === 'problem' && f.sizeCategory === 'normal') return false;
|
|
3939
|
+
if (microSizeFilter !== 'all' && microSizeFilter !== 'problem' && f.sizeCategory !== microSizeFilter) return false;
|
|
3940
|
+
if (microTypeFilter !== 'all' && f.type !== microTypeFilter) return false;
|
|
3941
|
+
if (q && !(`${f.path} ${f.type} ${(f.featureKeys || []).join(' ')} ${(f.risks || []).map(r => r.label).join(' ')}`.toLowerCase().includes(q))) return false;
|
|
3942
|
+
return true;
|
|
3943
|
+
});
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
function renderMicroservices(c) {
|
|
3947
|
+
const ms = D.microservices;
|
|
3948
|
+
if (!ms) {
|
|
3949
|
+
c.innerHTML = `<div class="empty-state">Нет данных microservices readiness. Обновите сканирование проекта.</div>`;
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3952
|
+
if (microDrillKey) {
|
|
3953
|
+
renderMicroserviceDetail(c, ms, microDrillKey);
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
const modules = filteredMicroModules(ms);
|
|
3958
|
+
const files = filteredMicroFiles(ms);
|
|
3959
|
+
const problemFiles = files.filter(f => f.sizeCategory !== 'normal');
|
|
3960
|
+
const riskFiles = (ms.topRisks || []).slice(0, 6);
|
|
3961
|
+
|
|
3962
|
+
c.innerHTML = `
|
|
3963
|
+
<div class="micro-summary-grid">
|
|
3964
|
+
<div class="micro-panel">
|
|
3965
|
+
<div class="micro-panel-title">Размерные бюджеты</div>
|
|
3966
|
+
<div class="micro-score">${ms.summary.godFiles + ms.summary.criticalFiles}</div>
|
|
3967
|
+
<div class="micro-subtle">God/critical files из ${ms.summary.sourceFiles} source-файлов. Large от ${ms.budgets.largeKb} KB или ${ms.budgets.largeLines} строк.</div>
|
|
3968
|
+
</div>
|
|
3969
|
+
<div class="micro-panel">
|
|
3970
|
+
<div class="micro-panel-title">Готовность модулей</div>
|
|
3971
|
+
<div class="micro-score" style="color:var(--green)">${ms.summary.readyModules}</div>
|
|
3972
|
+
<div class="micro-subtle">Ready >= 70. Risky < 40: <span style="color:${ms.summary.riskyModules ? 'var(--red)' : 'var(--text)'}">${ms.summary.riskyModules}</span>.</div>
|
|
3973
|
+
</div>
|
|
3974
|
+
<div class="micro-panel">
|
|
3975
|
+
<div class="micro-panel-title">Границы ownership</div>
|
|
3976
|
+
<div class="micro-score" style="color:${ms.summary.unmappedFiles || ms.summary.multiFeatureFiles ? 'var(--yellow)' : 'var(--green)'}">${ms.summary.unmappedFiles + ms.summary.multiFeatureFiles}</div>
|
|
3977
|
+
<div class="micro-subtle">Unknown ownership: ${ms.summary.unmappedFiles}. Multi-feature files: ${ms.summary.multiFeatureFiles}.</div>
|
|
3978
|
+
</div>
|
|
3979
|
+
</div>
|
|
3980
|
+
|
|
3981
|
+
<div class="micro-panel">
|
|
3982
|
+
<div class="micro-panel-title">Top risks</div>
|
|
3983
|
+
<div class="micro-risk-list">
|
|
3984
|
+
${riskFiles.length ? riskFiles.map(f => `
|
|
3985
|
+
<div class="micro-risk-item">
|
|
3986
|
+
<div>
|
|
3987
|
+
<div class="micro-path" title="${escapeHtml(f.path)}">${escapeHtml(f.path)}</div>
|
|
3988
|
+
${microRiskPills(f.risks, 4)}
|
|
3989
|
+
</div>
|
|
3990
|
+
<div style="text-align:right">
|
|
3991
|
+
<div style="color:${f.sizeCategory === 'critical' || f.sizeCategory === 'god' ? 'var(--red)' : 'var(--yellow)'};font-weight:700">${microCategoryLabel(f.sizeCategory)}</div>
|
|
3992
|
+
<div class="micro-subtle">${f.sizeKb} KB · ${f.lineCount} lines</div>
|
|
3993
|
+
</div>
|
|
3994
|
+
</div>`).join('') : `<div class="micro-subtle">Критичных рисков по текущим фильтрам нет.</div>`}
|
|
3995
|
+
</div>
|
|
3996
|
+
</div>
|
|
3997
|
+
|
|
3998
|
+
<div class="micro-section-head">
|
|
3999
|
+
<h3>Модули и готовность</h3>
|
|
4000
|
+
<span class="micro-subtle">${modules.length} из ${ms.modules.length}</span>
|
|
4001
|
+
</div>
|
|
4002
|
+
<table class="micro-table">
|
|
4003
|
+
<thead>
|
|
4004
|
+
<tr>
|
|
4005
|
+
<th>Модуль</th>
|
|
4006
|
+
<th>Score</th>
|
|
4007
|
+
<th>Файлы</th>
|
|
4008
|
+
<th>Largest file</th>
|
|
4009
|
+
<th>Boundary hints</th>
|
|
4010
|
+
<th>Top risks</th>
|
|
4011
|
+
</tr>
|
|
4012
|
+
</thead>
|
|
4013
|
+
<tbody>
|
|
4014
|
+
${modules.map(m => `
|
|
4015
|
+
<tr class="clickable micro-module-row" data-micro-key="${escapeHtml(m.key)}">
|
|
4016
|
+
<td>
|
|
4017
|
+
<div style="font-weight:700;color:var(--text)">${escapeHtml(m.label)}</div>
|
|
4018
|
+
<div class="micro-subtle">${escapeHtml(m.key)}</div>
|
|
4019
|
+
</td>
|
|
4020
|
+
<td>
|
|
4021
|
+
<div style="font-weight:700;color:${microScoreColor(m.readinessScore)}">${m.readinessScore}</div>
|
|
4022
|
+
<span class="micro-pill ${m.readinessTier}">${microTierLabel(m.readinessTier)}</span>
|
|
4023
|
+
</td>
|
|
4024
|
+
<td>
|
|
4025
|
+
<div>${m.fileCount} total</div>
|
|
4026
|
+
<div class="micro-subtle">${m.criticalFileCount + m.godFileCount} god/critical · ${m.testedCount} tested</div>
|
|
4027
|
+
</td>
|
|
4028
|
+
<td>
|
|
4029
|
+
${m.largestFile ? `<div class="micro-path" title="${escapeHtml(m.largestFile.path)}">${escapeHtml(m.largestFile.path)}</div><div class="micro-subtle">${m.largestFile.sizeKb} KB · ${m.largestFile.lineCount} lines</div>` : '<span class="micro-subtle">—</span>'}
|
|
4030
|
+
</td>
|
|
4031
|
+
<td><div class="micro-pill-row">${(m.boundaryHints || []).slice(0, 4).map(h => `<span class="micro-pill">${escapeHtml(h)}</span>`).join('') || '<span class="micro-subtle">—</span>'}</div></td>
|
|
4032
|
+
<td>${(m.topRisks || []).slice(0, 3).map(r => `<div class="micro-subtle">${escapeHtml(r)}</div>`).join('') || '<span class="micro-subtle">—</span>'}</td>
|
|
4033
|
+
</tr>`).join('')}
|
|
4034
|
+
</tbody>
|
|
4035
|
+
</table>
|
|
4036
|
+
|
|
4037
|
+
<div class="micro-section-head">
|
|
4038
|
+
<h3>God и large files</h3>
|
|
4039
|
+
<span class="micro-subtle">${problemFiles.length} файлов по фильтру</span>
|
|
4040
|
+
</div>
|
|
4041
|
+
${renderMicroFileTable(problemFiles)}
|
|
4042
|
+
`;
|
|
4043
|
+
|
|
4044
|
+
c.querySelectorAll('.micro-module-row').forEach(row => {
|
|
4045
|
+
row.onclick = () => {
|
|
4046
|
+
microDrillKey = row.dataset.microKey;
|
|
4047
|
+
renderContent();
|
|
4048
|
+
};
|
|
4049
|
+
});
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
function renderMicroFileTable(files) {
|
|
4053
|
+
const rows = files
|
|
4054
|
+
.sort((a, b) => b.sizeBytes - a.sizeBytes)
|
|
4055
|
+
.slice(0, 250)
|
|
4056
|
+
.map(f => `
|
|
4057
|
+
<tr>
|
|
4058
|
+
<td>
|
|
4059
|
+
<div class="micro-path" title="${escapeHtml(f.path)}">${escapeHtml(f.path)}</div>
|
|
4060
|
+
<div class="micro-subtle">${escapeHtml(f.type)} · deps ${f.dependencyCount}</div>
|
|
4061
|
+
</td>
|
|
4062
|
+
<td>
|
|
4063
|
+
<span class="micro-pill ${f.sizeCategory === 'normal' ? '' : f.sizeCategory === 'large' ? 'medium' : 'high'}">${microCategoryLabel(f.sizeCategory)}</span>
|
|
4064
|
+
</td>
|
|
4065
|
+
<td>${f.sizeKb} KB</td>
|
|
4066
|
+
<td>${f.lineCount}</td>
|
|
4067
|
+
<td>${microFeatureLabels(f.featureKeys)}</td>
|
|
4068
|
+
<td>${microRiskPills(f.risks, 4)}</td>
|
|
4069
|
+
</tr>`).join('');
|
|
4070
|
+
return `
|
|
4071
|
+
<table class="micro-table">
|
|
4072
|
+
<thead>
|
|
4073
|
+
<tr>
|
|
4074
|
+
<th>Файл</th>
|
|
4075
|
+
<th>Размер</th>
|
|
4076
|
+
<th>KB</th>
|
|
4077
|
+
<th>Строки</th>
|
|
4078
|
+
<th>Ownership</th>
|
|
4079
|
+
<th>Причины риска</th>
|
|
4080
|
+
</tr>
|
|
4081
|
+
</thead>
|
|
4082
|
+
<tbody>${rows || `<tr><td colspan="6"><div class="micro-subtle">Файлы не найдены по текущим фильтрам.</div></td></tr>`}</tbody>
|
|
4083
|
+
</table>`;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
function renderMicroserviceDetail(c, ms, key) {
|
|
4087
|
+
const module = (ms.modules || []).find(m => m.key === key);
|
|
4088
|
+
if (!module) {
|
|
4089
|
+
microDrillKey = null;
|
|
4090
|
+
renderMicroservices(c);
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
const files = filteredMicroFiles(ms, key).sort((a, b) => b.sizeBytes - a.sizeBytes);
|
|
4094
|
+
c.innerHTML = `
|
|
4095
|
+
<button class="micro-back" id="microBackBtn">← Назад к обзору</button>
|
|
4096
|
+
<div class="micro-section-head">
|
|
4097
|
+
<div>
|
|
4098
|
+
<h3>${escapeHtml(module.label)}</h3>
|
|
4099
|
+
<div class="micro-subtle">${escapeHtml(module.key)} · ${module.fileCount} files</div>
|
|
4100
|
+
</div>
|
|
4101
|
+
<div style="text-align:right">
|
|
4102
|
+
<div style="font-size:28px;font-weight:700;color:${microScoreColor(module.readinessScore)}">${module.readinessScore}</div>
|
|
4103
|
+
<span class="micro-pill ${module.readinessTier}">${microTierLabel(module.readinessTier)}</span>
|
|
4104
|
+
</div>
|
|
4105
|
+
</div>
|
|
4106
|
+
|
|
4107
|
+
<div class="micro-summary-grid">
|
|
4108
|
+
${[
|
|
4109
|
+
['Size health', module.sizeHealth],
|
|
4110
|
+
['Ownership clarity', module.ownershipClarity],
|
|
4111
|
+
['Test readiness', module.testReadiness],
|
|
4112
|
+
['Boundary readiness', module.boundaryReadiness],
|
|
4113
|
+
['Observability', module.observabilityReadiness],
|
|
4114
|
+
].map(([label, value]) => `
|
|
4115
|
+
<div class="micro-panel">
|
|
4116
|
+
<div class="micro-panel-title">${label}</div>
|
|
4117
|
+
<div class="micro-score" style="color:${microScoreColor(value)}">${value}</div>
|
|
4118
|
+
<div class="micro-score-bar"><div class="micro-score-fill" style="width:${value}%;background:${microScoreColor(value)}"></div></div>
|
|
4119
|
+
</div>`).join('')}
|
|
4120
|
+
</div>
|
|
4121
|
+
|
|
4122
|
+
<div class="micro-summary-grid">
|
|
4123
|
+
<div class="micro-panel">
|
|
4124
|
+
<div class="micro-panel-title">Boundary hints</div>
|
|
4125
|
+
<div class="micro-pill-row">${(module.boundaryHints || []).map(h => `<span class="micro-pill">${escapeHtml(h)}</span>`).join('') || '<span class="micro-subtle">Нет явных hints</span>'}</div>
|
|
4126
|
+
</div>
|
|
4127
|
+
<div class="micro-panel">
|
|
4128
|
+
<div class="micro-panel-title">Service map</div>
|
|
4129
|
+
<div class="micro-subtle">Nodes: ${module.serviceNodes.length ? module.serviceNodes.map(escapeHtml).join(', ') : '—'}</div>
|
|
4130
|
+
<div class="micro-subtle">Pipelines: ${module.pipelineIds.length ? module.pipelineIds.map(escapeHtml).join(', ') : '—'}</div>
|
|
4131
|
+
</div>
|
|
4132
|
+
<div class="micro-panel">
|
|
4133
|
+
<div class="micro-panel-title">Tests and ownership</div>
|
|
4134
|
+
<div class="micro-subtle">Tested: ${module.testedCount}/${module.fileCount}</div>
|
|
4135
|
+
<div class="micro-subtle">Stale tests: ${module.staleTestCount}</div>
|
|
4136
|
+
<div class="micro-subtle">Multi-feature files: ${module.multiFeatureFileCount}</div>
|
|
4137
|
+
</div>
|
|
4138
|
+
</div>
|
|
4139
|
+
|
|
4140
|
+
<div class="micro-section-head">
|
|
4141
|
+
<h3>Файлы модуля</h3>
|
|
4142
|
+
<span class="micro-subtle">${files.length} по текущим фильтрам</span>
|
|
4143
|
+
</div>
|
|
4144
|
+
${renderMicroFileTable(files)}
|
|
4145
|
+
`;
|
|
4146
|
+
document.getElementById('microBackBtn').onclick = () => {
|
|
4147
|
+
microDrillKey = null;
|
|
4148
|
+
renderContent();
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
// ─── Content ──────────────────────────────────────────────────────────────────
|
|
3697
4153
|
function renderContent() {
|
|
3698
4154
|
const c = document.getElementById('content');
|
|
3699
|
-
if (contextMode === 'observability') {
|
|
3700
|
-
renderObservability(c);
|
|
3701
|
-
return;
|
|
3702
|
-
}
|
|
3703
|
-
if (contextMode === '
|
|
4155
|
+
if (contextMode === 'observability') {
|
|
4156
|
+
renderObservability(c);
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
if (contextMode === 'microservices') {
|
|
4160
|
+
renderMicroservices(c);
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
4163
|
+
if (contextMode === 'docs') {
|
|
3704
4164
|
renderDocumentation(c);
|
|
3705
4165
|
return;
|
|
3706
4166
|
}
|
|
@@ -8503,8 +8963,11 @@ function getLoadPromptAggregate(summary) {
|
|
|
8503
8963
|
const values = endpoints.map(endpoint => numberOrNull(endpoint[field])).filter(v => v != null);
|
|
8504
8964
|
return values.length ? Math.max(...values) : null;
|
|
8505
8965
|
};
|
|
8966
|
+
const totalRequests = numberOrNull(summary?.totalRequests)
|
|
8967
|
+
?? numberOrNull(loadState?.totalRequests)
|
|
8968
|
+
?? (totalFromEndpoints || null);
|
|
8506
8969
|
return {
|
|
8507
|
-
totalRequests
|
|
8970
|
+
totalRequests,
|
|
8508
8971
|
rps: numberOrNull(summary?.rps),
|
|
8509
8972
|
avgDuration: numberOrNull(summary?.avgDuration) ?? weightedAvg,
|
|
8510
8973
|
p90Duration: numberOrNull(summary?.p90Duration) ?? maxEndpoint('p90Duration'),
|