viberadar 0.3.76 → 0.3.77
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 +159 -86
- package/package.json +1 -1
package/dist/ui/dashboard.html
CHANGED
|
@@ -670,8 +670,8 @@
|
|
|
670
670
|
}
|
|
671
671
|
.file-row-fix-btn:hover { opacity: 0.85; }
|
|
672
672
|
.obs-action-btn {
|
|
673
|
-
display: inline-flex; align-items: center; gap:
|
|
674
|
-
padding:
|
|
673
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
674
|
+
padding: 6px 14px; font-size: 12px; min-height: 32px;
|
|
675
675
|
background: transparent; border: 1px solid var(--border); border-radius: 4px;
|
|
676
676
|
color: var(--dim); cursor: pointer; white-space: nowrap; flex-shrink: 0;
|
|
677
677
|
transition: background 0.1s, color 0.1s, border-color 0.1s;
|
|
@@ -679,30 +679,66 @@
|
|
|
679
679
|
.obs-action-btn:hover { background: var(--accent); color: var(--bg); border-color: var(--accent); }
|
|
680
680
|
.obs-batch-btn { border-color: var(--yellow); color: var(--yellow); }
|
|
681
681
|
.obs-batch-btn:hover { background: rgba(255,200,0,0.15); color: var(--yellow); border-color: var(--yellow); }
|
|
682
|
-
.obs-expand-btn { background:none; border:none; color:var(--muted); cursor:pointer; font-size:
|
|
682
|
+
.obs-expand-btn { background:none; border:none; color:var(--muted); cursor:pointer; font-size:12px; padding:5px 8px; }
|
|
683
683
|
.obs-expand-btn:hover { color:var(--accent); }
|
|
684
684
|
.obs-detail { display:none; padding:6px 0 2px 0; border-top:1px dashed var(--border); margin-top:4px; }
|
|
685
685
|
.obs-detail.open { display:block; }
|
|
686
|
-
.obs-detail-list { max-height:
|
|
687
|
-
.obs-detail-item { display:flex; align-items:center; gap:6px; font-size:
|
|
688
|
-
.obs-detail-item input[type="checkbox"] { margin:0; flex-shrink:0; accent-color:var(--accent); }
|
|
686
|
+
.obs-detail-list { max-height:400px; overflow-y:auto; display:flex; flex-direction:column; gap:2px; }
|
|
687
|
+
.obs-detail-item { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); padding:6px 4px; }
|
|
688
|
+
.obs-detail-item input[type="checkbox"] { margin:0; flex-shrink:0; accent-color:var(--accent); width:18px; height:18px; }
|
|
689
689
|
.obs-detail-item span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
690
690
|
.obs-detail-bar { display:flex; align-items:center; gap:8px; margin-top:6px; padding-top:6px; border-top:1px dashed var(--border); }
|
|
691
|
-
.obs-run-selected { padding:
|
|
691
|
+
.obs-run-selected { padding:8px 16px; font-size:13px; font-weight:600; background:var(--accent); color:var(--bg); border:none; border-radius:4px; cursor:pointer; min-height:36px; }
|
|
692
692
|
.obs-run-selected:hover { opacity:0.85; }
|
|
693
693
|
.obs-run-selected:disabled { opacity:0.4; cursor:not-allowed; }
|
|
694
|
-
.obs-select-all { font-size:
|
|
694
|
+
.obs-select-all { font-size:12px; color:var(--dim); cursor:pointer; background:none; border:none; padding:4px 8px; }
|
|
695
695
|
.obs-select-all:hover { color:var(--accent); }
|
|
696
|
-
.obs-tier-badge { display:inline-block; padding:
|
|
696
|
+
.obs-tier-badge { display:inline-block; padding:3px 8px; border-radius:3px; font-size:11px; font-weight:700; letter-spacing:0.5px; }
|
|
697
697
|
.obs-tier-critical { background:rgba(248,81,73,0.2); color:var(--red); }
|
|
698
698
|
.obs-tier-important { background:rgba(227,179,65,0.2); color:var(--yellow); }
|
|
699
699
|
.obs-tier-normal { background:rgba(139,148,158,0.15); color:var(--muted); }
|
|
700
700
|
.obs-fp-list { font-size:11px; color:var(--muted); margin:4px 0 0 18px; }
|
|
701
701
|
.obs-fp-item { display:flex; gap:6px; padding:1px 0; align-items:baseline; }
|
|
702
|
-
.obs-fp-type { color:var(--yellow); font-weight:600; white-space:nowrap; font-size:
|
|
702
|
+
.obs-fp-type { color:var(--yellow); font-weight:600; white-space:nowrap; font-size:11px; }
|
|
703
703
|
.obs-fp-line { color:var(--dim); font-size:10px; flex-shrink:0; }
|
|
704
704
|
.obs-tier-group { margin-bottom:8px; }
|
|
705
705
|
.obs-tier-group-header { display:flex; align-items:center; gap:8px; padding:4px 0; font-size:12px; font-weight:600; color:var(--text); }
|
|
706
|
+
|
|
707
|
+
/* ── Observability tabs UX ── */
|
|
708
|
+
.obs-kpi-strip {
|
|
709
|
+
position:sticky; top:0; z-index:5;
|
|
710
|
+
display:grid; grid-template-columns:repeat(4,1fr); gap:8px;
|
|
711
|
+
background:var(--bg); padding:10px 0 12px 0; border-bottom:1px solid var(--border); margin-bottom:0;
|
|
712
|
+
}
|
|
713
|
+
.obs-kpi {
|
|
714
|
+
display:flex; align-items:baseline; gap:8px; padding:6px 10px;
|
|
715
|
+
background:var(--bg-card); border:1px solid var(--border); border-radius:6px;
|
|
716
|
+
}
|
|
717
|
+
.obs-kpi-value { font-size:18px; font-weight:700; font-variant-numeric:tabular-nums; }
|
|
718
|
+
.obs-kpi-label { font-size:11px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
719
|
+
.obs-tabs {
|
|
720
|
+
position:sticky; top:52px; z-index:4;
|
|
721
|
+
display:flex; background:var(--bg); border-radius:6px; padding:3px; gap:2px; margin:12px 0 16px 0;
|
|
722
|
+
}
|
|
723
|
+
.obs-tab {
|
|
724
|
+
flex:1; padding:8px 12px; text-align:center; font-size:12px; font-weight:600;
|
|
725
|
+
border-radius:4px; cursor:pointer; color:var(--muted); user-select:none;
|
|
726
|
+
transition:background 0.15s, color 0.15s; white-space:nowrap;
|
|
727
|
+
}
|
|
728
|
+
.obs-tab:hover { color:var(--text); background:var(--bg-hover); }
|
|
729
|
+
.obs-tab.active { background:var(--bg-card); color:var(--text); }
|
|
730
|
+
.obs-tab-badge {
|
|
731
|
+
display:inline-flex; align-items:center; justify-content:center;
|
|
732
|
+
min-width:18px; height:18px; padding:0 5px; border-radius:9px;
|
|
733
|
+
font-size:10px; font-weight:700; margin-left:6px; vertical-align:middle;
|
|
734
|
+
}
|
|
735
|
+
.obs-tab-badge.red { background:rgba(248,81,73,0.2); color:var(--red); }
|
|
736
|
+
.obs-tab-badge.yellow { background:rgba(227,179,65,0.2); color:var(--yellow); }
|
|
737
|
+
.obs-tab-badge.muted { background:rgba(139,148,158,0.15); color:var(--muted); }
|
|
738
|
+
.obs-tab-content { display:none; }
|
|
739
|
+
.obs-tab-content.active { display:block; }
|
|
740
|
+
@media (max-width:640px) { .obs-kpi-strip { grid-template-columns:repeat(2,1fr); } }
|
|
741
|
+
|
|
706
742
|
.file-row-err-badge {
|
|
707
743
|
display: inline-flex; align-items: center;
|
|
708
744
|
font-size: 11px; padding: 1px 6px; border-radius: 10px;
|
|
@@ -1182,6 +1218,17 @@ let fileRowsRenderKey = '';
|
|
|
1182
1218
|
let fileRowsRenderLimit = FILE_ROWS_INITIAL_LIMIT;
|
|
1183
1219
|
let e2ePlan = null; // current E2E plan object
|
|
1184
1220
|
let e2ePlanLoading = false;
|
|
1221
|
+
let obsActiveTab = 'overview'; // active observability tab
|
|
1222
|
+
|
|
1223
|
+
function switchObsTab(tabId) {
|
|
1224
|
+
obsActiveTab = tabId;
|
|
1225
|
+
history.replaceState(null, '', location.pathname + location.search + '#obs-tab=' + tabId);
|
|
1226
|
+
document.querySelectorAll('.obs-tab').forEach(t =>
|
|
1227
|
+
t.classList.toggle('active', t.dataset.obstab === tabId));
|
|
1228
|
+
document.querySelectorAll('.obs-tab-content').forEach(p => {
|
|
1229
|
+
p.classList.toggle('active', p.dataset.obstab === tabId);
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1185
1232
|
const modeStore = {
|
|
1186
1233
|
qa: { view: 'features', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false },
|
|
1187
1234
|
observability: { view: 'files', searchQuery: '', activeTypes: new Set(), drillFeatureKey: null, drillTestType: null, activePanelKey: null, showOnlyUntestedInFeature: false },
|
|
@@ -2280,6 +2327,8 @@ async function init() {
|
|
|
2280
2327
|
|
|
2281
2328
|
contextMode = getModeFromPath();
|
|
2282
2329
|
view = D.hasConfig ? 'features' : 'files';
|
|
2330
|
+
const _obsHash = window.location.hash;
|
|
2331
|
+
if (_obsHash.startsWith('#obs-tab=')) obsActiveTab = _obsHash.replace('#obs-tab=', '');
|
|
2283
2332
|
applyHashRoute();
|
|
2284
2333
|
|
|
2285
2334
|
if (!D.hasConfig) {
|
|
@@ -2493,7 +2542,7 @@ function renderObservability(c) {
|
|
|
2493
2542
|
// Store catalog for buttons to reference by index (avoids inline JSON in onclick)
|
|
2494
2543
|
window.__obsCatalog = o.catalog;
|
|
2495
2544
|
|
|
2496
|
-
const noisyRows = o.topNoisyPatterns.
|
|
2545
|
+
const noisyRows = o.topNoisyPatterns.map(i => {
|
|
2497
2546
|
const safePattern = escapeHtml(i.pattern).replace(/'/g, ''');
|
|
2498
2547
|
const btn = hasAgent
|
|
2499
2548
|
? `<button class="obs-action-btn" onclick="event.stopPropagation();runAgentTask('obs-suppress-pattern',null,null,null,{pattern:'${safePattern}',recommendation:'${i.recommendation}'})">убрать</button>`
|
|
@@ -2595,7 +2644,7 @@ function renderObservability(c) {
|
|
|
2595
2644
|
</div>`;
|
|
2596
2645
|
}).join('') || '<div class="obs-sub" style="color:var(--green)">Все обязательные поля на месте</div>';
|
|
2597
2646
|
|
|
2598
|
-
const catalogRows = o.catalog.
|
|
2647
|
+
const catalogRows = o.catalog.map((i, idx) => {
|
|
2599
2648
|
const missing = (i.missingFields || []);
|
|
2600
2649
|
const missingStr = missing.length ? missing.join(', ') : '—';
|
|
2601
2650
|
const btn = hasAgent
|
|
@@ -2612,110 +2661,134 @@ function renderObservability(c) {
|
|
|
2612
2661
|
${btn}
|
|
2613
2662
|
</div>`}).join('');
|
|
2614
2663
|
|
|
2664
|
+
const noisyCount = o.topNoisyPatterns.length;
|
|
2665
|
+
const missingCount = v2Data.length;
|
|
2666
|
+
const enrichCount = fieldGapEntries.length;
|
|
2667
|
+
const catalogCount = o.catalog.length;
|
|
2668
|
+
|
|
2615
2669
|
c.innerHTML = `
|
|
2616
2670
|
<div class="onboarding-block">
|
|
2617
2671
|
<h3>Наблюдаемость: что это?</h3>
|
|
2618
2672
|
<p>Аудит покрытия логами: что добавить, что убрать, что обогатить — на основе статического анализа лог-вызовов.</p>
|
|
2619
2673
|
</div>
|
|
2620
2674
|
|
|
2621
|
-
<div class="obs-
|
|
2622
|
-
<div class="obs-
|
|
2623
|
-
<
|
|
2624
|
-
<
|
|
2625
|
-
<div class="obs-sub">Доля шумных лог-вызовов из всех.</div>
|
|
2675
|
+
<div class="obs-kpi-strip">
|
|
2676
|
+
<div class="obs-kpi">
|
|
2677
|
+
<span class="obs-kpi-value" style="color:${metricColor(noiseRatio,10,30,true)}">${noiseRatio}%</span>
|
|
2678
|
+
<span class="obs-kpi-label">Шум</span>
|
|
2626
2679
|
</div>
|
|
2627
|
-
<div class="obs-
|
|
2628
|
-
<
|
|
2629
|
-
<
|
|
2630
|
-
<div class="obs-sub">Логи с обязательными полями (module, event, traceId).</div>
|
|
2680
|
+
<div class="obs-kpi">
|
|
2681
|
+
<span class="obs-kpi-value" style="color:${metricColor(structPct,80,50,false)}">${structPct}%</span>
|
|
2682
|
+
<span class="obs-kpi-label">Структурированность</span>
|
|
2631
2683
|
</div>
|
|
2632
|
-
<div class="obs-
|
|
2633
|
-
<
|
|
2634
|
-
<
|
|
2635
|
-
<div class="obs-sub">ERROR-логи с контекстом для диагностики.</div>
|
|
2684
|
+
<div class="obs-kpi">
|
|
2685
|
+
<span class="obs-kpi-value" style="color:${metricColor(actionPct,80,50,false)}">${actionPct}%</span>
|
|
2686
|
+
<span class="obs-kpi-label">Actionable ошибки</span>
|
|
2636
2687
|
</div>
|
|
2637
|
-
<div class="obs-
|
|
2638
|
-
<
|
|
2639
|
-
<
|
|
2640
|
-
<div class="obs-sub">Модули с хотя бы одним warn/error событием.</div>
|
|
2688
|
+
<div class="obs-kpi">
|
|
2689
|
+
<span class="obs-kpi-value" style="color:${metricColor(coveragePct,80,50,false)}">${coveragePct}%</span>
|
|
2690
|
+
<span class="obs-kpi-label">Покрытие сценариев</span>
|
|
2641
2691
|
</div>
|
|
2642
2692
|
</div>
|
|
2643
2693
|
|
|
2644
|
-
<div class="obs-
|
|
2645
|
-
<div class="obs-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2694
|
+
<div class="obs-tabs">
|
|
2695
|
+
<div class="obs-tab" data-obstab="overview">Обзор</div>
|
|
2696
|
+
<div class="obs-tab" data-obstab="remove">Убрать${noisyCount ? `<span class="obs-tab-badge red">${noisyCount}</span>` : ''}</div>
|
|
2697
|
+
<div class="obs-tab" data-obstab="add">Добавить${missingCount ? `<span class="obs-tab-badge yellow">${missingCount}</span>` : ''}</div>
|
|
2698
|
+
<div class="obs-tab" data-obstab="enrich">Обогатить${enrichCount ? `<span class="obs-tab-badge yellow">${enrichCount}</span>` : ''}</div>
|
|
2699
|
+
<div class="obs-tab" data-obstab="catalog">Каталог${catalogCount ? `<span class="obs-tab-badge muted">${catalogCount}</span>` : ''}</div>
|
|
2700
|
+
</div>
|
|
2701
|
+
|
|
2702
|
+
<div class="obs-tab-content" data-obstab="overview">
|
|
2703
|
+
<div class="obs-grid" style="grid-template-columns:1fr 1fr 1fr;margin-bottom:12px">
|
|
2704
|
+
<div class="obs-card">
|
|
2705
|
+
<div class="obs-title">Источники по формату</div>
|
|
2706
|
+
<div class="obs-list">
|
|
2707
|
+
${sourceByFormat.map(g => `
|
|
2708
|
+
<div class="obs-list-item">
|
|
2709
|
+
<span style="color:${g.color}">${g.label}</span>
|
|
2710
|
+
<strong>${g.count}</strong>
|
|
2711
|
+
</div>`).join('') || '<div class="obs-sub">Нет данных</div>'}
|
|
2712
|
+
</div>
|
|
2653
2713
|
</div>
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2714
|
+
<div class="obs-card">
|
|
2715
|
+
<div class="obs-title">Классификация логов</div>
|
|
2716
|
+
<div class="obs-list">
|
|
2717
|
+
<div class="obs-list-item"><span style="color:var(--red)">Мусор (убрать)</span><strong>${o.classification.trash}</strong></div>
|
|
2718
|
+
<div class="obs-list-item"><span style="color:var(--green)">Полезные</span><strong>${o.classification.useful}</strong></div>
|
|
2719
|
+
<div class="obs-list-item"><span style="color:var(--blue)">Критичные</span><strong>${o.classification.critical}</strong></div>
|
|
2720
|
+
</div>
|
|
2661
2721
|
</div>
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
</div
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2722
|
+
<div class="obs-card">
|
|
2723
|
+
<div class="obs-title">Рекомендации</div>
|
|
2724
|
+
<div class="obs-list" style="gap:2px">
|
|
2725
|
+
${['suppress','enrich fields','add event','downgrade level'].map(rec => {
|
|
2726
|
+
const items = o.catalog.filter(c => c.recommendation === rec);
|
|
2727
|
+
if (!items.length) return '';
|
|
2728
|
+
const groupId = 'rec-' + rec.replace(/\s+/g, '-');
|
|
2729
|
+
const expandBtn = hasAgent ? `<button class="obs-expand-btn" onclick="event.stopPropagation();toggleObsDetail('${groupId}')">▼</button>` : '';
|
|
2730
|
+
const detailItems = items.map((ci, i) => {
|
|
2731
|
+
const catIdx = o.catalog.indexOf(ci);
|
|
2732
|
+
const mf = (ci.missingFields||[]).join(', ') || '—';
|
|
2733
|
+
return `<label class="obs-detail-item"><input type="checkbox" data-idx="${catIdx}" onchange="obsUpdateSelectedCount('${groupId}')"><span title="${escapeHtml(ci.modulePath)}">${escapeHtml(ci.modulePath.split('/').slice(-2).join('/'))}</span><span style="color:var(--dim);flex-shrink:0">${ci.format}</span></label>`;
|
|
2734
|
+
}).join('');
|
|
2735
|
+
const detail = hasAgent ? `
|
|
2736
|
+
<div id="obs-detail-${groupId}" class="obs-detail">
|
|
2737
|
+
<div class="obs-detail-bar" style="border-top:none;padding-top:0;margin-bottom:4px">
|
|
2738
|
+
<button class="obs-select-all" onclick="obsToggleAll('${groupId}')">выбрать все / снять</button>
|
|
2739
|
+
</div>
|
|
2740
|
+
<div class="obs-detail-list">${detailItems}</div>
|
|
2741
|
+
<div class="obs-detail-bar">
|
|
2742
|
+
<button class="obs-run-selected" disabled onclick="obsRunSelected('${groupId}','obs-fix-selected',{recommendationType:'${rec}'})">исправить выбранные</button>
|
|
2743
|
+
</div>
|
|
2744
|
+
</div>` : '';
|
|
2745
|
+
return `<div>
|
|
2746
|
+
<div class="obs-list-item"><span>${recLabels[rec]}</span><strong>${items.length}</strong>${expandBtn}</div>
|
|
2747
|
+
${detail}
|
|
2748
|
+
</div>`;
|
|
2749
|
+
}).join('')}
|
|
2750
|
+
</div>
|
|
2691
2751
|
</div>
|
|
2692
2752
|
</div>
|
|
2693
2753
|
</div>
|
|
2694
2754
|
|
|
2695
|
-
<div class="obs-
|
|
2755
|
+
<div class="obs-tab-content" data-obstab="remove">
|
|
2696
2756
|
<div class="obs-card">
|
|
2697
|
-
<div class="obs-title"
|
|
2757
|
+
<div class="obs-title">Шумные паттерны</div>
|
|
2698
2758
|
<div class="obs-list" style="gap:4px">${noisyRows}</div>
|
|
2699
2759
|
</div>
|
|
2760
|
+
</div>
|
|
2761
|
+
|
|
2762
|
+
<div class="obs-tab-content" data-obstab="add">
|
|
2700
2763
|
<div class="obs-card">
|
|
2701
|
-
<div class="obs-title"
|
|
2764
|
+
<div class="obs-title">Нет критичных логов</div>
|
|
2702
2765
|
<div class="obs-list" style="gap:4px">${missingSection}</div>
|
|
2703
2766
|
</div>
|
|
2704
2767
|
</div>
|
|
2705
2768
|
|
|
2706
|
-
<div class="obs-
|
|
2707
|
-
<div class="obs-
|
|
2708
|
-
|
|
2709
|
-
|
|
2769
|
+
<div class="obs-tab-content" data-obstab="enrich">
|
|
2770
|
+
<div class="obs-card">
|
|
2771
|
+
<div class="obs-title">Пробелы по полям</div>
|
|
2772
|
+
<div class="obs-sub" style="margin-bottom:8px">Обязательные поля по стандарту: service, env, trace_id, request_id, event_name, outcome, error_code (warn/error), user_id.</div>
|
|
2773
|
+
<div class="obs-list">${fieldGapRows}</div>
|
|
2774
|
+
</div>
|
|
2710
2775
|
</div>
|
|
2711
2776
|
|
|
2712
|
-
<div class="obs-
|
|
2713
|
-
<div class="obs-
|
|
2714
|
-
|
|
2715
|
-
<div class="obs-
|
|
2716
|
-
|
|
2777
|
+
<div class="obs-tab-content" data-obstab="catalog">
|
|
2778
|
+
<div class="obs-card">
|
|
2779
|
+
<div class="obs-title">Каталог источников логов</div>
|
|
2780
|
+
<div class="obs-catalog" style="margin-top:8px;border:none;padding:0">
|
|
2781
|
+
<div class="obs-cat-row head"><span>модуль</span><span>уровень</span><span>формат</span><span>пробелы</span><span>не хватает</span><span>действие</span><span></span></div>
|
|
2782
|
+
${catalogRows || '<div class="obs-row">Логи не найдены</div>'}
|
|
2783
|
+
</div>
|
|
2717
2784
|
</div>
|
|
2718
2785
|
</div>`;
|
|
2786
|
+
|
|
2787
|
+
// Attach tab click handlers and restore active tab
|
|
2788
|
+
c.querySelectorAll('.obs-tab').forEach(tab => {
|
|
2789
|
+
tab.onclick = () => switchObsTab(tab.dataset.obstab);
|
|
2790
|
+
});
|
|
2791
|
+
switchObsTab(obsActiveTab);
|
|
2719
2792
|
}
|
|
2720
2793
|
|
|
2721
2794
|
function backToFeatureDetail() {
|