brakit 0.9.2 → 0.10.1
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/api.d.ts +148 -5
- package/dist/api.js +232 -54
- package/dist/bin/brakit.js +228 -35
- package/dist/dashboard-client.global.js +742 -385
- package/dist/dashboard.html +933 -432
- package/dist/mcp/server.js +99 -21
- package/dist/runtime/index.js +1551 -430
- package/package.json +1 -1
package/dist/dashboard.html
CHANGED
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
--red:#dc2626;
|
|
19
19
|
--cyan:#0891b2;
|
|
20
20
|
--green-bg:rgba(22,163,74,0.08);--green-bg-subtle:rgba(22,163,74,0.05);--green-border:rgba(22,163,74,0.2);--green-border-subtle:rgba(22,163,74,0.15);
|
|
21
|
-
--amber-bg:rgba(217,119,6,0.
|
|
21
|
+
--amber-bg:rgba(217,119,6,0.08);--amber-border:rgba(217,119,6,0.15);
|
|
22
|
+
--red-bg:rgba(220,38,38,0.08);--red-border:rgba(220,38,38,0.2);
|
|
23
|
+
--blue-bg:rgba(37,99,235,0.08);--cyan-bg:rgba(8,145,178,0.07);
|
|
24
|
+
--accent-bg:rgba(99,102,241,0.08);
|
|
22
25
|
--sidebar-width:232px;--header-height:52px;
|
|
23
26
|
--radius:8px;--radius-sm:6px;
|
|
24
27
|
--shadow-sm:0 1px 3px rgba(0,0,0,0.06),0 1px 2px rgba(0,0,0,0.03);
|
|
@@ -65,6 +68,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|
|
65
68
|
.sidebar-logo .logo-version{font-weight:400;font-size:11px;color:var(--text-muted);margin-left:8px;letter-spacing:0}
|
|
66
69
|
.sidebar-nav{padding:12px;flex:1}
|
|
67
70
|
.sidebar-section{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);padding:16px 12px 8px}
|
|
71
|
+
.sidebar-divider{height:1px;background:var(--border-subtle);margin:8px 12px}
|
|
68
72
|
.sidebar-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:var(--radius);color:var(--text-dim);font-size:14px;font-weight:500;cursor:pointer;transition:all .15s;border:none;background:transparent;width:100%;text-align:left;font-family:var(--sans)}
|
|
69
73
|
.sidebar-item:hover{background:var(--bg-hover);color:var(--text)}
|
|
70
74
|
.sidebar-item.active{background:var(--bg-active);color:var(--accent)}
|
|
@@ -73,6 +77,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|
|
73
77
|
.sidebar-item:hover .item-icon{opacity:.8}
|
|
74
78
|
.sidebar-item .item-label{flex:1}
|
|
75
79
|
.sidebar-item .item-count{font-size:12px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:2px 8px;border-radius:10px;min-width:24px;text-align:center}
|
|
80
|
+
.sidebar-beta{font-size:9px;color:#6366f1;background:#eef2ff;border:1px solid #e0e7ff;border-radius:4px;padding:0 5px;margin-left:auto;line-height:16px}
|
|
76
81
|
.sidebar-item.disabled{opacity:.35;cursor:default;pointer-events:none}
|
|
77
82
|
.sidebar-item .coming-soon{font-size:10px;color:var(--text-muted);background:var(--bg-muted);padding:2px 8px;border-radius:10px;font-weight:600;letter-spacing:.3px}
|
|
78
83
|
.sidebar-footer{padding:16px 24px;border-top:1px solid var(--border-subtle);font-size:12px;color:var(--text-muted);font-family:var(--mono)}
|
|
@@ -97,7 +102,8 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|
|
97
102
|
/* Content */
|
|
98
103
|
.main-content{flex:1;overflow-y:auto}
|
|
99
104
|
bk-dashboard{display:contents}
|
|
100
|
-
bk-overview-view,bk-flows-view,bk-requests-view,bk-fetches-view,bk-queries-view,bk-errors-view,bk-logs-view,bk-security-view,bk-performance-view,bk-timeline-panel,bk-empty-state{display:block}
|
|
105
|
+
bk-overview-view,bk-flows-view,bk-requests-view,bk-fetches-view,bk-queries-view,bk-errors-view,bk-logs-view,bk-security-view,bk-performance-view,bk-explorer-view,bk-insights-view,bk-timeline-panel,bk-empty-state{display:block}
|
|
106
|
+
bk-graph-view{display:block}
|
|
101
107
|
bk-method-badge,bk-status-pill,bk-duration-label,bk-copy-button{display:inline-flex;flex-shrink:0}
|
|
102
108
|
bk-stat-card{display:inline-flex}
|
|
103
109
|
bk-toast{display:block;position:fixed;top:0;left:0;right:0;z-index:100;pointer-events:none}
|
|
@@ -443,53 +449,25 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
443
449
|
.perf-trend-slower{color:var(--red)}
|
|
444
450
|
.perf-trend-faster{color:var(--green)}
|
|
445
451
|
|
|
446
|
-
|
|
447
|
-
.ov-container{padding:24px 28px}
|
|
448
|
-
|
|
449
|
-
/* Summary banner */
|
|
450
|
-
.ov-summary{display:flex;gap:10px;margin-bottom:24px;flex-wrap:wrap}
|
|
451
|
-
.ov-stat{display:flex;flex-direction:column;gap:4px;flex:1;min-width:100px;padding:16px 18px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-sm);transition:box-shadow var(--transition, .15s ease)}
|
|
452
|
-
.ov-stat:hover{box-shadow:var(--shadow-md)}
|
|
453
|
-
.ov-stat-value{font-size:22px;font-weight:700;font-family:var(--mono);color:var(--text);line-height:1.2}
|
|
454
|
-
.ov-stat-label{font-size:10px;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);font-weight:600}
|
|
455
|
-
|
|
456
|
-
/* Section header */
|
|
457
|
-
.ov-section-title{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);margin-bottom:12px;display:flex;align-items:center;gap:8px}
|
|
458
|
-
.ov-issue-count{font-size:11px;font-family:var(--mono);color:var(--text-dim);background:var(--bg-muted);border:1px solid var(--border);padding:1px 8px;border-radius:10px}
|
|
459
|
-
|
|
460
|
-
/* Insight cards */
|
|
461
|
-
.ov-cards{display:flex;flex-direction:column;gap:8px}
|
|
462
|
-
.ov-card{display:flex;align-items:flex-start;gap:14px;padding:16px 20px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;transition:all var(--transition, .15s ease);box-shadow:var(--shadow-sm)}
|
|
463
|
-
.ov-card:hover{background:var(--bg-hover);border-color:var(--border-light);box-shadow:var(--shadow-md);transform:translateY(-1px)}
|
|
464
|
-
.ov-card-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:10px;border-radius:50%;margin-top:2px}
|
|
465
|
-
.ov-card-icon.critical{background:rgba(220,38,38,.08);color:var(--red)}
|
|
466
|
-
.ov-card-icon.warning{background:rgba(217,119,6,.08);color:var(--amber)}
|
|
467
|
-
.ov-card-icon.info{background:rgba(37,99,235,.08);color:var(--blue)}
|
|
468
|
-
.ov-card-icon.resolved{background:var(--green-bg);color:var(--green)}
|
|
469
|
-
.ov-card-body{flex:1;min-width:0}
|
|
470
|
-
.ov-card-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:2px}
|
|
471
|
-
.ov-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
|
|
472
|
-
.ov-card-detail{font-size:11px;font-family:var(--mono);color:var(--text-muted);margin-top:6px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);line-height:1.5}
|
|
473
|
-
.ov-card-desc strong{color:var(--text);font-family:var(--mono);font-weight:600}
|
|
474
|
-
.ov-card-arrow{color:var(--text-muted);font-size:12px;flex-shrink:0;margin-top:2px;font-family:var(--mono);transition:transform .15s}
|
|
475
|
-
|
|
476
|
-
/* Expanded card */
|
|
477
|
-
.ov-card.expanded{border-color:var(--border-light);box-shadow:var(--shadow-md)}
|
|
478
|
-
.ov-card-expand{display:none;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}
|
|
479
|
-
.ov-card-hint{font-size:12px;color:var(--text-dim);line-height:1.5;margin-bottom:10px}
|
|
480
|
-
.ov-card-link{font-size:12px;font-weight:600;color:var(--blue);cursor:pointer;display:inline-block;padding:4px 0}
|
|
481
|
-
.ov-card-link:hover{text-decoration:underline}
|
|
482
|
-
.ov-detail-label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
|
|
483
|
-
.ov-detail-item{font-size:12px;color:var(--text);font-family:var(--mono);padding:2px 0}
|
|
484
|
-
|
|
485
|
-
/* All-clear banner */
|
|
486
|
-
.ov-clear{display:flex;align-items:center;gap:12px;padding:16px 20px;background:var(--green-bg-subtle);border:1px solid var(--green-border);border-radius:var(--radius);color:var(--green);font-size:13px;font-weight:500}
|
|
487
|
-
.ov-clear-icon{font-size:16px}
|
|
452
|
+
.ov-container{padding:28px}
|
|
488
453
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
.ov-card-
|
|
492
|
-
.ov-card-
|
|
454
|
+
.ov-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
|
455
|
+
|
|
456
|
+
.ov-card-nav{display:flex;align-items:center;gap:16px;padding:20px 24px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;cursor:pointer;transition:all .18s ease;box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
|
457
|
+
.ov-card-nav:hover{border-color:var(--border-light);box-shadow:0 4px 16px rgba(0,0,0,.06);transform:translateY(-2px)}
|
|
458
|
+
.ov-card-nav:active{transform:translateY(0)}
|
|
459
|
+
|
|
460
|
+
.ov-card-empty{opacity:.55}
|
|
461
|
+
.ov-card-empty:hover{opacity:.8}
|
|
462
|
+
|
|
463
|
+
.ov-card-icon-lg{font-size:22px;width:40px;height:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--bg-muted);border-radius:10px;color:var(--text-muted)}
|
|
464
|
+
|
|
465
|
+
.ov-card-content{flex:1;min-width:0}
|
|
466
|
+
.ov-card-headline{font-size:16px;font-weight:700;color:var(--text);font-family:var(--mono);line-height:1.3}
|
|
467
|
+
.ov-card-context{font-size:12px;color:var(--text-muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
468
|
+
|
|
469
|
+
.ov-card-arrow{font-size:16px;color:var(--text-muted);opacity:0;transition:opacity .15s,transform .15s;flex-shrink:0}
|
|
470
|
+
.ov-card-nav:hover .ov-card-arrow{opacity:1;transform:translateX(2px)}
|
|
493
471
|
|
|
494
472
|
/* Security tab */
|
|
495
473
|
.sec-container{padding:24px 28px}
|
|
@@ -743,98 +721,264 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
743
721
|
opacity: .9;
|
|
744
722
|
font-size: 10px;
|
|
745
723
|
}
|
|
724
|
+
|
|
725
|
+
.graph-wrapper{display:flex;flex-direction:column;height:calc(100vh - 120px);outline:none}
|
|
726
|
+
|
|
727
|
+
/* Toolbar — centered search, layers left, flow picker right */
|
|
728
|
+
.graph-toolbar{display:flex;align-items:center;gap:10px;padding:8px 16px;border-bottom:1px solid var(--border)}
|
|
729
|
+
|
|
730
|
+
/* Layer toggles */
|
|
731
|
+
.graph-layer-toggles{display:flex;gap:4px;flex-shrink:0}
|
|
732
|
+
.graph-layer-btn{display:flex;align-items:center;gap:3px;font-size:10px;font-weight:500;padding:3px 8px;border:1px solid var(--border);border-radius:12px;background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s;white-space:nowrap}
|
|
733
|
+
.graph-layer-btn:hover{border-color:var(--text-muted);color:var(--text)}
|
|
734
|
+
.graph-layer-btn.active{background:var(--bg-card);font-weight:600}
|
|
735
|
+
|
|
736
|
+
/* Search — takes remaining space, centered */
|
|
737
|
+
.graph-search{flex:1;position:relative;display:flex;align-items:center;max-width:360px;margin:0 auto}
|
|
738
|
+
.graph-search-icon{position:absolute;left:10px;color:var(--text-muted);font-size:13px;pointer-events:none;opacity:0.5}
|
|
739
|
+
.graph-search-input{width:100%;font-size:11px;padding:6px 28px 6px 28px;border:1px solid var(--border);border-radius:8px;background:var(--bg);color:var(--text);font-family:var(--mono);outline:none;transition:border-color .15s,box-shadow .15s}
|
|
740
|
+
.graph-search-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.1)}
|
|
741
|
+
.graph-search-input::placeholder{color:var(--text-muted);opacity:0.5}
|
|
742
|
+
.graph-search-clear{position:absolute;right:8px;background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:11px;padding:0 4px;line-height:1;border-radius:3px}
|
|
743
|
+
.graph-search-clear:hover{color:var(--text);background:var(--bg-card)}
|
|
744
|
+
|
|
745
|
+
/* Flow picker */
|
|
746
|
+
.graph-flow-picker{font-size:10px;padding:4px 8px;border:1px solid var(--border);border-radius:8px;background:var(--bg);color:var(--text);cursor:pointer;font-family:var(--mono);max-width:200px;flex-shrink:0}
|
|
747
|
+
|
|
748
|
+
/* Auth legend — inline in toolbar */
|
|
749
|
+
.graph-auth-legend{display:flex;gap:8px;align-items:center;font-size:10px;color:var(--text-muted);flex-shrink:0}
|
|
750
|
+
.graph-auth-legend-item{display:flex;align-items:center;gap:3px;white-space:nowrap}
|
|
751
|
+
|
|
752
|
+
/* Canvas */
|
|
753
|
+
.graph-body{display:flex;flex:1;min-height:0}
|
|
754
|
+
.graph-canvas{flex:1;overflow:hidden;padding:0;position:relative;min-height:0}
|
|
755
|
+
.graph-svg{display:block}
|
|
756
|
+
.graph-col-header{fill:#c4c4cc;font-size:9px;font-weight:600;font-family:'Inter',system-ui,sans-serif;letter-spacing:1.5px}
|
|
757
|
+
|
|
758
|
+
/* Floating controls — bottom-center pill */
|
|
759
|
+
.graph-float{position:absolute;top:12px;right:12px;display:flex;align-items:center;gap:2px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:4px 6px;box-shadow:0 2px 12px rgba(0,0,0,.08);z-index:10}
|
|
760
|
+
.graph-float-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:13px;padding:4px 8px;line-height:1;border-radius:6px;transition:all .12s;white-space:nowrap}
|
|
761
|
+
.graph-float-btn:hover{background:var(--bg);color:var(--text)}
|
|
762
|
+
.graph-float-btn-accent{font-size:11px;font-weight:600;color:#6366f1}
|
|
763
|
+
.graph-float-btn-accent:hover{background:rgba(99,102,241,.08);color:#4f46e5}
|
|
764
|
+
.graph-float-zoom{font-size:10px;color:var(--text-muted);font-family:var(--mono);min-width:36px;text-align:center;user-select:none}
|
|
765
|
+
.graph-float-sep{width:1px;height:16px;background:var(--border);margin:0 2px;flex-shrink:0}
|
|
766
|
+
|
|
767
|
+
/* Empty & loading states */
|
|
768
|
+
.graph-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:400px;color:var(--text-muted);text-align:center;padding:40px}
|
|
769
|
+
.graph-empty-icon{font-size:40px;opacity:0.25;margin-bottom:12px}
|
|
770
|
+
.graph-empty-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:6px}
|
|
771
|
+
.graph-empty-desc{font-size:12px;max-width:320px;line-height:1.5}
|
|
772
|
+
.graph-loading{display:flex;align-items:center;justify-content:center;min-height:400px;color:var(--text-muted);font-size:13px}
|
|
773
|
+
|
|
774
|
+
/* Detail panel */
|
|
775
|
+
.graph-detail{width:320px;border-left:1px solid var(--border);overflow-y:auto;padding:16px;background:var(--bg-card);flex-shrink:0;max-height:calc(100vh - 160px)}
|
|
776
|
+
.graph-detail-head{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}
|
|
777
|
+
.graph-detail-badge{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px}
|
|
778
|
+
.graph-detail-name{font-size:14px;font-weight:700;color:var(--text);word-break:break-all;font-family:var(--mono)}
|
|
779
|
+
.graph-detail-close{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;padding:0 4px;line-height:1}
|
|
780
|
+
.graph-detail-close:hover{color:var(--text)}
|
|
781
|
+
|
|
782
|
+
.graph-detail-auth-badge{display:inline-block;font-size:10px;font-weight:500;color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;border-radius:4px;padding:1px 6px;margin-top:4px}
|
|
783
|
+
.graph-detail-mw-badge{display:inline-block;font-size:10px;font-weight:500;color:#6b7280;background:#f3f4f6;border:1px solid #d1d5db;border-radius:4px;padding:1px 6px;margin-top:4px;margin-left:4px}
|
|
784
|
+
|
|
785
|
+
/* Detail tabs */
|
|
786
|
+
.graph-detail-tabs{display:flex;gap:2px;margin-bottom:12px;border-bottom:1px solid var(--border);padding-bottom:0}
|
|
787
|
+
.graph-detail-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--text-muted);cursor:pointer;font-size:11px;font-weight:500;padding:6px 10px;transition:all .12s}
|
|
788
|
+
.graph-detail-tab:hover{color:var(--text)}
|
|
789
|
+
.graph-detail-tab.active{color:#6366f1;border-bottom-color:#6366f1}
|
|
790
|
+
|
|
791
|
+
/* Detail stats */
|
|
792
|
+
.graph-detail-stats{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}
|
|
793
|
+
.graph-detail-stat{background:var(--bg);border-radius:var(--radius-sm);padding:10px 12px}
|
|
794
|
+
.graph-detail-val{font-size:20px;font-weight:700;font-family:var(--mono);color:var(--text);line-height:1.2}
|
|
795
|
+
.graph-detail-lbl{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.6px;margin-top:2px}
|
|
796
|
+
|
|
797
|
+
/* Detail sections */
|
|
798
|
+
.graph-detail-sec{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.8px;margin:12px 0 8px;padding-top:10px;border-top:1px solid var(--border)}
|
|
799
|
+
.graph-detail-conn{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text);padding:6px 8px;background:var(--bg);border-radius:var(--radius-sm);margin-bottom:4px;font-family:var(--mono)}
|
|
800
|
+
.graph-detail-edge-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
801
|
+
.graph-detail-edge-type{font-size:10px;font-weight:600;text-transform:uppercase;min-width:42px}
|
|
802
|
+
.graph-detail-dim{color:var(--text-muted);font-size:10px;margin-left:auto;white-space:nowrap}
|
|
803
|
+
.graph-detail-sql{font-size:10px;color:var(--text-muted);padding:8px 10px;background:var(--bg);border-radius:var(--radius-sm);font-family:var(--mono);word-break:break-all;line-height:1.5;margin:0 0 4px;white-space:pre-wrap;border:1px solid var(--border)}
|
|
804
|
+
|
|
805
|
+
/* Security findings in detail */
|
|
806
|
+
.graph-detail-finding{padding:8px 10px;background:var(--bg);border-radius:var(--radius-sm);margin-bottom:6px;border:1px solid var(--border)}
|
|
807
|
+
.graph-detail-finding-title{font-size:12px;font-weight:600;color:var(--text);margin-top:4px}
|
|
808
|
+
.graph-detail-finding-meta{font-size:10px;color:var(--text-muted);margin-top:2px;font-family:var(--mono)}
|
|
809
|
+
.graph-detail-severity{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;padding:1px 6px;border-radius:3px}
|
|
810
|
+
.graph-detail-severity-critical{background:#fef2f2;color:#dc2626;border:1px solid #fecaca}
|
|
811
|
+
.graph-detail-severity-warning{background:#fffbeb;color:#d97706;border:1px solid #fde68a}
|
|
812
|
+
.graph-detail-severity-info{background:#eff6ff;color:#2563eb;border:1px solid #bfdbfe}
|
|
813
|
+
|
|
814
|
+
/* Issues in detail */
|
|
815
|
+
.graph-detail-issue-summary{margin-bottom:12px}
|
|
816
|
+
.graph-detail-hint{font-size:11px;color:var(--text-muted);line-height:1.5;margin:0}
|
|
817
|
+
.graph-detail-empty{font-size:12px;color:var(--text-muted);padding:16px;text-align:center}
|
|
818
|
+
|
|
819
|
+
/* Pulse animation for critical security badges */
|
|
820
|
+
@keyframes graph-pulse{0%,100%{opacity:1}50%{opacity:0.5}}
|
|
821
|
+
.graph-pulse{animation:graph-pulse 2s ease-in-out infinite}
|
|
822
|
+
|
|
823
|
+
/* Flow edge animation */
|
|
824
|
+
@keyframes graph-flow-dash{to{stroke-dashoffset:-24}}
|
|
825
|
+
.graph-flow-edge{animation:graph-flow-dash 1s linear infinite}
|
|
826
|
+
|
|
827
|
+
/* Explorer sub-tabs */
|
|
828
|
+
.explorer-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);padding:0 28px;background:var(--bg);position:sticky;top:0;z-index:2}
|
|
829
|
+
.explorer-tab{padding:10px 16px;font-size:13px;font-weight:500;color:var(--text-muted);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px;font-family:var(--sans);white-space:nowrap}
|
|
830
|
+
.explorer-tab:hover{color:var(--text)}
|
|
831
|
+
.explorer-tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
832
|
+
.explorer-tab-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 6px;border-radius:8px}
|
|
833
|
+
.explorer-tab.active .explorer-tab-count{color:var(--accent);background:var(--accent-bg)}
|
|
834
|
+
|
|
835
|
+
/* Insights filter chips */
|
|
836
|
+
.insights-filters{display:flex;gap:6px;padding:16px 28px;border-bottom:1px solid var(--border);background:var(--bg);position:sticky;top:0;z-index:2}
|
|
837
|
+
.insights-chip{font-size:12px;font-weight:500;padding:5px 14px;border:1px solid var(--border);border-radius:20px;background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:5px;font-family:var(--sans)}
|
|
838
|
+
.insights-chip:hover{border-color:var(--text-muted);color:var(--text)}
|
|
839
|
+
.insights-chip.active{background:var(--accent);color:white;border-color:var(--accent)}
|
|
840
|
+
.insights-chip-count{font-size:10px;font-family:var(--mono);background:rgba(0,0,0,.08);padding:1px 5px;border-radius:8px}
|
|
841
|
+
.insights-chip.active .insights-chip-count{background:rgba(255,255,255,.25)}
|
|
842
|
+
|
|
843
|
+
/* Insights card list */
|
|
844
|
+
.insights-list{padding:16px 28px}
|
|
845
|
+
|
|
846
|
+
.insights-empty{display:flex;align-items:center;gap:10px;padding:24px;color:var(--green);font-size:14px;font-weight:500}
|
|
847
|
+
.insights-empty-icon{font-size:18px}
|
|
848
|
+
|
|
849
|
+
.insights-card{display:flex;align-items:flex-start;gap:12px;padding:14px 18px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s;margin-bottom:8px}
|
|
850
|
+
.insights-card:hover{border-color:var(--border-light);box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
|
851
|
+
.insights-card.expanded{border-color:var(--border-light);box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
|
852
|
+
.insights-card.resolved{opacity:.55}
|
|
853
|
+
.insights-card.resolved:hover{opacity:.8}
|
|
854
|
+
|
|
855
|
+
.insights-card-left{flex-shrink:0;padding-top:2px}
|
|
856
|
+
.insights-sev{width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:10px;border-radius:50%}
|
|
857
|
+
.insights-sev.critical{background:var(--red-bg);color:var(--red)}
|
|
858
|
+
.insights-sev.warning{background:var(--amber-bg);color:var(--amber)}
|
|
859
|
+
.insights-sev.info{background:var(--blue-bg);color:var(--blue)}
|
|
860
|
+
|
|
861
|
+
.insights-card-body{flex:1;min-width:0}
|
|
862
|
+
.insights-card-header{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:3px}
|
|
863
|
+
.insights-card-title{font-size:13px;font-weight:600;color:var(--text)}
|
|
864
|
+
.insights-card-title.resolved{text-decoration:line-through;color:var(--text-muted)}
|
|
865
|
+
.insights-card-cat{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);background:var(--bg-muted);padding:1px 6px;border-radius:4px}
|
|
866
|
+
.insights-card-count{font-size:11px;font-family:var(--mono);color:var(--text-muted)}
|
|
867
|
+
.insights-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
|
|
868
|
+
.insights-card-detail{font-size:11px;font-family:var(--mono);color:var(--text-muted);margin-top:6px;padding:6px 10px;background:var(--bg-muted);border:1px solid var(--border-subtle);border-radius:6px;line-height:1.5}
|
|
869
|
+
.insights-card-progress{font-size:11px;color:var(--text-muted);margin-top:4px;font-family:var(--mono)}
|
|
870
|
+
.insights-card-hint{font-size:12px;color:var(--text-dim);line-height:1.6;margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}
|
|
871
|
+
|
|
872
|
+
.insights-badge-regressed{font-size:9px;font-weight:700;color:var(--red);background:var(--red-bg);padding:1px 6px;border-radius:4px}
|
|
873
|
+
.insights-badge-verifying{font-size:9px;font-weight:700;color:var(--amber);background:var(--amber-bg);padding:1px 6px;border-radius:4px}
|
|
874
|
+
.insights-badge-resolved{font-size:9px;font-weight:700;color:var(--green);background:var(--green-bg);padding:1px 6px;border-radius:4px}
|
|
875
|
+
|
|
876
|
+
.insights-card-arrow{color:var(--text-muted);font-size:12px;flex-shrink:0;padding-top:2px;font-family:var(--mono);transition:transform .15s}
|
|
877
|
+
|
|
878
|
+
.insights-section{display:flex;align-items:center;gap:8px;padding:14px 0 8px;margin-top:4px;font-size:12px;font-weight:700;color:var(--text);text-transform:uppercase;letter-spacing:.5px;border-top:1px solid var(--border);user-select:none}
|
|
879
|
+
.insights-section:first-child{border-top:none;margin-top:0}
|
|
880
|
+
.insights-section-icon{font-size:11px;width:16px;text-align:center}
|
|
881
|
+
.insights-section-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 7px;border-radius:8px;font-weight:500}
|
|
882
|
+
.insights-section-regressed{color:var(--red)}
|
|
883
|
+
.insights-section-regressed .insights-section-count{color:var(--red);background:var(--red-bg)}
|
|
884
|
+
.insights-section-verifying{color:var(--amber)}
|
|
885
|
+
.insights-section-verifying .insights-section-count{color:var(--amber);background:var(--amber-bg)}
|
|
886
|
+
.insights-section-resolved{color:var(--green)}
|
|
887
|
+
.insights-section-resolved .insights-section-count{color:var(--green);background:var(--green-bg)}
|
|
888
|
+
.insights-section-dismissed{color:var(--text-muted);cursor:pointer}
|
|
889
|
+
.insights-section-dismissed:hover{color:var(--text)}
|
|
746
890
|
</style>
|
|
747
891
|
</head>
|
|
748
892
|
<body>
|
|
749
893
|
<bk-dashboard></bk-dashboard>
|
|
750
894
|
<script>window.__BRAKIT_CONFIG__={port:{{PORT}},version:"{{VERSION}}"};</script>
|
|
751
|
-
<script>(function(){'use strict';var
|
|
752
|
-
\f\r]`,
|
|
753
|
-
\f\r"'\`<>=]|("|')|))|$)`,"g"),Ne=/'/g,ke=/"/g,Pe=/^(?:script|style|textarea|title)$/i,te=i=>(e,...t)=>({_$litType$:i,strings:e,values:t}),a=te(1),j=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),De=new WeakMap,G=W.createTreeWalker(W,129);function Ue(i,e){if(!Zt(i)||!i.hasOwnProperty("raw"))throw Error("invalid template strings array");return Le!==void 0?Le.createHTML(e):e}var qs=(i,e)=>{let t=i.length-1,s=[],r,o=e===2?"<svg>":e===3?"<math>":"",n=vt;for(let l=0;l<t;l++){let c=i[l],p,h,m=-1,E=0;for(;E<c.length&&(n.lastIndex=E,h=n.exec(c),h!==null);)E=n.lastIndex,n===vt?h[1]==="!--"?n=Me:h[1]!==void 0?n=Oe:h[2]!==void 0?(Pe.test(h[2])&&(r=RegExp("</"+h[2],"g")),n=B):h[3]!==void 0&&(n=B):n===B?h[0]===">"?(n=r??vt,m=-1):h[1]===void 0?m=-2:(m=n.lastIndex-h[2].length,p=h[1],n=h[3]===void 0?B:h[3]==='"'?ke:Ne):n===ke||n===Ne?n=B:n===Me||n===Oe?n=vt:(n=B,r=void 0);let T=n===B&&i[l+1].startsWith("/>")?" ":"";o+=n===vt?c+Ds:m>=0?(s.push(p),c.slice(0,m)+He+c.slice(m)+D+T):c+D+(m===-2?l:T);}return [Ue(i,o+(i[t]||"<?>")+(e===2?"</svg>":e===3?"</math>":"")),s]},bt=class i{constructor({strings:e,_$litType$:t},s){let r;this.parts=[];let o=0,n=0,l=e.length-1,c=this.parts,[p,h]=qs(e,t);if(this.el=i.createElement(p,s),G.currentNode=this.el.content,t===2||t===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes);}for(;(r=G.nextNode())!==null&&c.length<l;){if(r.nodeType===1){if(r.hasAttributes())for(let m of r.getAttributeNames())if(m.endsWith(He)){let E=h[n++],T=r.getAttribute(m).split(D),b=/([.?@])?(.*)/.exec(E);c.push({type:1,index:o,name:b[2],strings:T,ctor:b[1]==="."?Xt:b[1]==="?"?Kt:b[1]==="@"?zt:z}),r.removeAttribute(m);}else m.startsWith(D)&&(c.push({type:6,index:o}),r.removeAttribute(m));if(Pe.test(r.tagName)){let m=r.textContent.split(D),E=m.length-1;if(E>0){r.textContent=kt?kt.emptyScript:"";for(let T=0;T<E;T++)r.append(m[T],gt()),G.nextNode(),c.push({type:2,index:++o});r.append(m[E],gt());}}}else if(r.nodeType===8)if(r.data===qe)c.push({type:2,index:o});else {let m=-1;for(;(m=r.data.indexOf(D,m+1))!==-1;)c.push({type:7,index:o}),m+=D.length-1;}o++;}}static createElement(e,t){let s=W.createElement("template");return s.innerHTML=e,s}};function K(i,e,t=i,s){if(e===j)return e;let r=s!==void 0?t._$Co?.[s]:t._$Cl,o=Et(e)?void 0:e._$litDirective$;return r?.constructor!==o&&(r?._$AO?.(false),o===void 0?r=void 0:(r=new o(i),r._$AT(i,t,s)),s!==void 0?(t._$Co??(t._$Co=[]))[s]=r:t._$Cl=r),r!==void 0&&(e=K(i,r._$AS(i,e.values),r,s)),e}var Yt=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:s}=this._$AD,r=(e?.creationScope??W).importNode(t,true);G.currentNode=r;let o=G.nextNode(),n=0,l=0,c=s[0];for(;c!==void 0;){if(n===c.index){let p;c.type===2?p=new _t(o,o.nextSibling,this,e):c.type===1?p=new c.ctor(o,c.name,c.strings,this,e):c.type===6&&(p=new Jt(o,this,e)),this._$AV.push(p),c=s[++l];}n!==c?.index&&(o=G.nextNode(),n++);}return G.currentNode=W,r}p(e){let t=0;for(let s of this._$AV)s!==void 0&&(s.strings!==void 0?(s._$AI(e,s,t),t+=s.strings.length-2):s._$AI(e[t])),t++;}},_t=class i{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,s,r){this.type=2,this._$AH=d,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=s,this.options=r,this._$Cv=r?.isConnected??true;}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=K(this,e,t),Et(e)?e===d||e==null||e===""?(this._$AH!==d&&this._$AR(),this._$AH=d):e!==this._$AH&&e!==j&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):Hs(e)?this.k(e):this._(e);}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e));}_(e){this._$AH!==d&&Et(this._$AH)?this._$AA.nextSibling.data=e:this.T(W.createTextNode(e)),this._$AH=e;}$(e){let{values:t,_$litType$:s}=e,r=typeof s=="number"?this._$AC(e):(s.el===void 0&&(s.el=bt.createElement(Ue(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===r)this._$AH.p(t);else {let o=new Yt(r,this),n=o.u(this.options);o.p(t),this.T(n),this._$AH=o;}}_$AC(e){let t=De.get(e.strings);return t===void 0&&De.set(e.strings,t=new bt(e)),t}k(e){Zt(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,s,r=0;for(let o of e)r===t.length?t.push(s=new i(this.O(gt()),this.O(gt()),this,this.options)):s=t[r],s._$AI(o),r++;r<t.length&&(this._$AR(s&&s._$AB.nextSibling,r),t.length=r);}_$AR(e=this._$AA.nextSibling,t){for(this._$AP?.(false,true,t);e!==this._$AB;){let s=Ie(e).nextSibling;Ie(e).remove(),e=s;}}setConnected(e){this._$AM===void 0&&(this._$Cv=e,this._$AP?.(e));}},z=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,t,s,r,o){this.type=1,this._$AH=d,this._$AN=void 0,this.element=e,this.name=t,this._$AM=r,this.options=o,s.length>2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d;}_$AI(e,t=this,s,r){let o=this.strings,n=false;if(o===void 0)e=K(this,e,t,0),n=!Et(e)||e!==this._$AH&&e!==j,n&&(this._$AH=e);else {let l=e,c,p;for(e=o[0],c=0;c<o.length-1;c++)p=K(this,l[s+c],t,c),p===j&&(p=this._$AH[c]),n||(n=!Et(p)||p!==this._$AH[c]),p===d?e=d:e!==d&&(e+=(p??"")+o[c+1]),this._$AH[c]=p;}n&&!r&&this.j(e);}j(e){e===d?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,e??"");}},Xt=class extends z{constructor(){super(...arguments),this.type=3;}j(e){this.element[this.name]=e===d?void 0:e;}},Kt=class extends z{constructor(){super(...arguments),this.type=4;}j(e){this.element.toggleAttribute(this.name,!!e&&e!==d);}},zt=class extends z{constructor(e,t,s,r,o){super(e,t,s,r,o),this.type=5;}_$AI(e,t=this){if((e=K(this,e,t,0)??d)===j)return;let s=this._$AH,r=e===d&&s!==d||e.capture!==s.capture||e.once!==s.once||e.passive!==s.passive,o=e!==d&&(s===d||r);r&&this.element.removeEventListener(this.name,this,s),o&&this.element.addEventListener(this.name,this,e),this._$AH=e;}handleEvent(e){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,e):this._$AH.handleEvent(e);}},Jt=class{constructor(e,t,s){this.element=e,this.type=6,this._$AN=void 0,this._$AM=t,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(e){K(this,e);}};var Ps=ft.litHtmlPolyfillSupport;Ps?.(bt,_t),(ft.litHtmlVersions??(ft.litHtmlVersions=[])).push("3.3.2");var Fe=(i,e,t)=>{let s=t?.renderBefore??e,r=s._$litPart$;if(r===void 0){let o=t?.renderBefore??null;s._$litPart$=r=new _t(e.insertBefore(gt(),o),o,void 0,t??{});}return r._$AI(i),r};var St=globalThis,f=class extends O{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){var t;let e=super.createRenderRoot();return (t=this.renderOptions).renderBefore??(t.renderBefore=e.firstChild),e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=Fe(t,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return j}};f._$litElement$=true,f.finalized=true,St.litElementHydrateSupport?.({LitElement:f});var Us=St.litElementPolyfillSupport;Us?.({LitElement:f});(St.litElementVersions??(St.litElementVersions=[])).push("4.2.2");var g=i=>(e,t)=>{t!==void 0?t.addInitializer(()=>{customElements.define(i,e);}):customElements.define(i,e);};var Fs={attribute:true,type:String,converter:mt,reflect:false,hasChanged:Nt},Bs=(i=Fs,e,t)=>{let{kind:s,metadata:r}=t,o=globalThis.litPropertyMetadata.get(r);if(o===void 0&&globalThis.litPropertyMetadata.set(r,o=new Map),s==="setter"&&((i=Object.create(i)).wrapped=true),o.set(t.name,i),s==="accessor"){let{name:n}=t;return {set(l){let c=e.get.call(this);e.set.call(this,l),this.requestUpdate(n,c,i,true,l);},init(l){return l!==void 0&&this.C(n,void 0,i,l),l}}}if(s==="setter"){let{name:n}=t;return function(l){let c=this[n];e.call(this,l),this.requestUpdate(n,c,i,true,l);}}throw Error("Unsupported decorator location: "+s)};function y(i){return (e,t)=>typeof t=="object"?Bs(i,e,t):((s,r,o)=>{let n=r.hasOwnProperty(o);return r.constructor.createProperty(o,s),n?Object.getOwnPropertyDescriptor(r,o):void 0})(i,e,t)}function _(i){return y({...i,state:true,attribute:false})}var Tt=class extends f{constructor(){super(...arguments);this.method="";}createRenderRoot(){return this}render(){let t=this.method.toUpperCase();return a`<span class="method-badge method-badge-${t}">${t}</span>`}};u([y()],Tt.prototype,"method",2),Tt=u([g("bk-method-badge")],Tt);var I="/__brakit/api",N="/__brakit",$={flows:`${I}/flows`,requests:`${I}/requests`,events:`${I}/events`,clear:`${I}/clear`,fetches:`${I}/fetches`,errors:`${I}/errors`,logs:`${I}/logs`,queries:`${I}/queries`,metricsLive:`${I}/metrics/live`,insights:`${I}/insights`,tab:`${I}/tab`,activity:`${I}/activity`};var J="polling",Ht="static",Gs="auth-handshake",Ws="auth-check",js="middleware",$t={[Gs]:1,[Ws]:1,[js]:1};var ee="fetch";var se="error_event",re="query",oe="issues";var ie={overview:"Overview",actions:"Actions",requests:"Requests",fetches:"Server Fetches",queries:"Queries",errors:"Errors",logs:"Logs",performance:"Performance",security:"Security"},ne={overview:"Live summary of your application",actions:"User actions captured as sequences of HTTP requests",requests:"All HTTP requests proxied through brakit",fetches:"Outbound HTTP calls made by your server to external services",queries:"Database queries executed during request handling",errors:"Unhandled exceptions and errors thrown by your application",logs:"Console output from your application",performance:"Endpoint health and response time trends",security:"Security findings and recommendations"};var le=100,Z=300,tt=800,ce=2e3,de=100,pe=50,ue=500;var H="__all__",Ft={SELECT:"var(--blue)",INSERT:"var(--green)",UPDATE:"var(--amber)",DELETE:"var(--red)",COUNT:"var(--text-muted)"},Ke={error:"var(--red)",warn:"var(--amber)",info:"var(--blue)",debug:"var(--text-muted)",log:"var(--text-dim)"},me=["#2563eb","#7c3aed","#16a34a","#d97706","#dc2626","#0891b2","#ea580c","#c026d3","#059669","#db2777"],yt={green:"#4ade80",amber:"#fbbf24",red:"#f87171"},et=[{max:le,label:"Fast",color:"var(--green)",bg:"rgba(22,163,74,0.08)",border:"rgba(22,163,74,0.2)"},{max:Z,label:"Good",color:"var(--green)",bg:"rgba(22,163,74,0.06)",border:"rgba(22,163,74,0.15)"},{max:tt,label:"OK",color:"var(--amber)",bg:"rgba(217,119,6,0.06)",border:"rgba(217,119,6,0.15)"},{max:ce,label:"Slow",color:"var(--red)",bg:"rgba(220,38,38,0.06)",border:"rgba(220,38,38,0.15)"},{max:1/0,label:"Critical",color:"var(--red)",bg:"rgba(220,38,38,0.08)",border:"rgba(220,38,38,0.2)"}],ze="rgba(228,228,231,0.8)",ve="rgba(113,113,122,0.7)",Je="10px monospace",fe="9px monospace";var Ze={top:16,right:16,bottom:28,left:52},ts={fetch:"var(--blue)",log:"var(--text-muted)",error:"var(--red)",query:"var(--accent)"},es={fetch:"FETCH",log:"LOG",error:"ERROR",query:"QUERY"},ss=new Set(["cookie","set-cookie","authorization","proxy-authorization","x-api-key","x-auth-token"]),rs={400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",408:"Timeout",409:"Conflict",422:"Unprocessable",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},os=new Set(["host","connection","accept-encoding"]),q={critical:{icon:"\u2717",cls:"critical",sort:0},warning:{icon:"\u26A0",cls:"warning",sort:1},info:{icon:"\u2139",cls:"info",sort:2}};function v(i){return i<1e3?i+"ms":(i/1e3).toFixed(1)+"s"}function U(i){return !i||i===0?"":i<1024?i+"b":(i/1024).toFixed(1)+"kb"}function P(i){return i?i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}function st(i){return i>=500?"status-pill-5xx":i>=400?"status-pill-4xx":i>=300?"status-pill-3xx":"status-pill-2xx"}function is(i){return rs[i]||(i>=500?"Server Error":i>=400?"Client Error":"OK")}function Qs(i,e){if(ss.has(i.toLowerCase())){let t=String(e);return t.length<=8?"****":t.slice(0,4)+"..."+t.slice(-4)+" ("+t.length+" chars)"}return String(e)}function rt(i){return !i||Object.keys(i).length===0?'<span style="color:var(--text-muted)">No headers</span>':Object.entries(i).map(([e,t])=>'<span class="json-key">'+P(e)+"</span>: "+P(Qs(e,t))).join(`
|
|
754
|
-
`)}function
|
|
895
|
+
<script>(function(){'use strict';var kr=Object.defineProperty;var Or=Object.getOwnPropertyDescriptor;var u=(o,r,t,e)=>{for(var s=e>1?void 0:e?Or(r,t):r,i=o.length-1,n;i>=0;i--)(n=o[i])&&(s=(e?n(r,t,s):n(s))||s);return e&&s&&kr(r,t,s),s};var oe=globalThis,ae=oe.ShadowRoot&&(oe.ShadyCSS===void 0||oe.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ms=Symbol(),us=new WeakMap,ne=class{constructor(r,t,e){if(this._$cssResult$=true,e!==ms)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=r,this.t=t;}get styleSheet(){let r=this.o,t=this.t;if(ae&&r===void 0){let e=t!==void 0&&t.length===1;e&&(r=us.get(t)),r===void 0&&((this.o=r=new CSSStyleSheet).replaceSync(this.cssText),e&&us.set(t,r));}return r}toString(){return this.cssText}},fs=o=>new ne(typeof o=="string"?o:o+"",void 0,ms);var vs=(o,r)=>{if(ae)o.adoptedStyleSheets=r.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of r){let e=document.createElement("style"),s=oe.litNonce;s!==void 0&&e.setAttribute("nonce",s),e.textContent=t.cssText,o.appendChild(e);}},Ae=ae?o=>o:o=>o instanceof CSSStyleSheet?(r=>{let t="";for(let e of r.cssRules)t+=e.cssText;return fs(t)})(o):o;var{is:Dr,defineProperty:Pr,getOwnPropertyDescriptor:Hr,getOwnPropertyNames:qr,getOwnPropertySymbols:Ur,getPrototypeOf:Fr}=Object,et=globalThis,gs=et.trustedTypes,Gr=gs?gs.emptyScript:"",Br=et.reactiveElementPolyfillSupport,Pt=(o,r)=>o,Ht={toAttribute(o,r){switch(r){case Boolean:o=o?Gr:null;break;case Object:case Array:o=o==null?o:JSON.stringify(o);}return o},fromAttribute(o,r){let t=o;switch(r){case Boolean:t=o!==null;break;case Number:t=o===null?null:Number(o);break;case Object:case Array:try{t=JSON.parse(o);}catch{t=null;}}return t}},le=(o,r)=>!Dr(o,r),Es={attribute:true,type:String,converter:Ht,reflect:false,useDefault:false,hasChanged:le};Symbol.metadata??(Symbol.metadata=Symbol("metadata")),et.litPropertyMetadata??(et.litPropertyMetadata=new WeakMap);var K=class extends HTMLElement{static addInitializer(r){this._$Ei(),(this.l??(this.l=[])).push(r);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(r,t=Es){if(t.state&&(t.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(r)&&((t=Object.create(t)).wrapped=true),this.elementProperties.set(r,t),!t.noAccessor){let e=Symbol(),s=this.getPropertyDescriptor(r,e,t);s!==void 0&&Pr(this.prototype,r,s);}}static getPropertyDescriptor(r,t,e){let{get:s,set:i}=Hr(this.prototype,r)??{get(){return this[t]},set(n){this[t]=n;}};return {get:s,set(n){let a=s?.call(this);i?.call(this,n),this.requestUpdate(r,a,e);},configurable:true,enumerable:true}}static getPropertyOptions(r){return this.elementProperties.get(r)??Es}static _$Ei(){if(this.hasOwnProperty(Pt("elementProperties")))return;let r=Fr(this);r.finalize(),r.l!==void 0&&(this.l=[...r.l]),this.elementProperties=new Map(r.elementProperties);}static finalize(){if(this.hasOwnProperty(Pt("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(Pt("properties"))){let t=this.properties,e=[...qr(t),...Ur(t)];for(let s of e)this.createProperty(s,t[s]);}let r=this[Symbol.metadata];if(r!==null){let t=litPropertyMetadata.get(r);if(t!==void 0)for(let[e,s]of t)this.elementProperties.set(e,s);}this._$Eh=new Map;for(let[t,e]of this.elementProperties){let s=this._$Eu(t,e);s!==void 0&&this._$Eh.set(s,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(r){let t=[];if(Array.isArray(r)){let e=new Set(r.flat(1/0).reverse());for(let s of e)t.unshift(Ae(s));}else r!==void 0&&t.push(Ae(r));return t}static _$Eu(r,t){let e=t.attribute;return e===false?void 0:typeof e=="string"?e:typeof r=="string"?r.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise(r=>this.enableUpdating=r),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(r=>r(this));}addController(r){(this._$EO??(this._$EO=new Set)).add(r),this.renderRoot!==void 0&&this.isConnected&&r.hostConnected?.();}removeController(r){this._$EO?.delete(r);}_$E_(){let r=new Map,t=this.constructor.elementProperties;for(let e of t.keys())this.hasOwnProperty(e)&&(r.set(e,this[e]),delete this[e]);r.size>0&&(this._$Ep=r);}createRenderRoot(){let r=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return vs(r,this.constructor.elementStyles),r}connectedCallback(){this.renderRoot??(this.renderRoot=this.createRenderRoot()),this.enableUpdating(true),this._$EO?.forEach(r=>r.hostConnected?.());}enableUpdating(r){}disconnectedCallback(){this._$EO?.forEach(r=>r.hostDisconnected?.());}attributeChangedCallback(r,t,e){this._$AK(r,e);}_$ET(r,t){let e=this.constructor.elementProperties.get(r),s=this.constructor._$Eu(r,e);if(s!==void 0&&e.reflect===true){let i=(e.converter?.toAttribute!==void 0?e.converter:Ht).toAttribute(t,e.type);this._$Em=r,i==null?this.removeAttribute(s):this.setAttribute(s,i),this._$Em=null;}}_$AK(r,t){let e=this.constructor,s=e._$Eh.get(r);if(s!==void 0&&this._$Em!==s){let i=e.getPropertyOptions(s),n=typeof i.converter=="function"?{fromAttribute:i.converter}:i.converter?.fromAttribute!==void 0?i.converter:Ht;this._$Em=s;let a=n.fromAttribute(t,i.type);this[s]=a??this._$Ej?.get(s)??a,this._$Em=null;}}requestUpdate(r,t,e,s=false,i){if(r!==void 0){let n=this.constructor;if(s===false&&(i=this[r]),e??(e=n.getPropertyOptions(r)),!((e.hasChanged??le)(i,t)||e.useDefault&&e.reflect&&i===this._$Ej?.get(r)&&!this.hasAttribute(n._$Eu(r,e))))return;this.C(r,t,e);}this.isUpdatePending===false&&(this._$ES=this._$EP());}C(r,t,{useDefault:e,reflect:s,wrapped:i},n){e&&!(this._$Ej??(this._$Ej=new Map)).has(r)&&(this._$Ej.set(r,n??t??this[r]),i!==true||n!==void 0)||(this._$AL.has(r)||(this.hasUpdated||e||(t=void 0),this._$AL.set(r,t)),s===true&&this._$Em!==r&&(this._$Eq??(this._$Eq=new Set)).add(r));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}let r=this.scheduleUpdate();return r!=null&&await r,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??(this.renderRoot=this.createRenderRoot()),this._$Ep){for(let[s,i]of this._$Ep)this[s]=i;this._$Ep=void 0;}let e=this.constructor.elementProperties;if(e.size>0)for(let[s,i]of e){let{wrapped:n}=i,a=this[s];n!==true||this._$AL.has(s)||a===void 0||this.C(s,void 0,i,a);}}let r=false,t=this._$AL;try{r=this.shouldUpdate(t),r?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM();}catch(e){throw r=false,this._$EM(),e}r&&this._$AE(t);}willUpdate(r){}_$AE(r){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(r)),this.updated(r);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(r){return true}update(r){this._$Eq&&(this._$Eq=this._$Eq.forEach(t=>this._$ET(t,this[t]))),this._$EM();}updated(r){}firstUpdated(r){}};K.elementStyles=[],K.shadowRootOptions={mode:"open"},K[Pt("elementProperties")]=new Map,K[Pt("finalized")]=new Map,Br?.({ReactiveElement:K}),(et.reactiveElementVersions??(et.reactiveElementVersions=[])).push("2.1.2");var Ut=globalThis,bs=o=>o,ce=Ut.trustedTypes,ys=ce?ce.createPolicy("lit-html",{createHTML:o=>o}):void 0,ws="$lit$",st=`lit$${Math.random().toFixed(9).slice(2)}$`,Rs="?"+st,Wr=`<${Rs}>`,ct=document,Ft=()=>ct.createComment(""),Gt=o=>o===null||typeof o!="object"&&typeof o!="function",Oe=Array.isArray,Qr=o=>Oe(o)||typeof o?.[Symbol.iterator]=="function",Ie=`[
|
|
896
|
+
\f\r]`,qt=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_s=/-->/g,$s=/>/g,at=RegExp(`>|${Ie}(?:([^\\s"'>=/]+)(${Ie}*=${Ie}*(?:[^
|
|
897
|
+
\f\r"'\`<>=]|("|')|))|$)`,"g"),xs=/'/g,Ss=/"/g,As=/^(?:script|style|textarea|title)$/i,De=o=>(r,...t)=>({_$litType$:o,strings:r,values:t}),l=De(1),k=De(2),z=Symbol.for("lit-noChange"),p=Symbol.for("lit-nothing"),Ts=new WeakMap,lt=ct.createTreeWalker(ct,129);function Is(o,r){if(!Oe(o)||!o.hasOwnProperty("raw"))throw Error("invalid template strings array");return ys!==void 0?ys.createHTML(r):r}var jr=(o,r)=>{let t=o.length-1,e=[],s,i=r===2?"<svg>":r===3?"<math>":"",n=qt;for(let a=0;a<t;a++){let c=o[a],d,h,m=-1,f=0;for(;f<c.length&&(n.lastIndex=f,h=n.exec(c),h!==null);)f=n.lastIndex,n===qt?h[1]==="!--"?n=_s:h[1]!==void 0?n=$s:h[2]!==void 0?(As.test(h[2])&&(s=RegExp("</"+h[2],"g")),n=at):h[3]!==void 0&&(n=at):n===at?h[0]===">"?(n=s??qt,m=-1):h[1]===void 0?m=-2:(m=n.lastIndex-h[2].length,d=h[1],n=h[3]===void 0?at:h[3]==='"'?Ss:xs):n===Ss||n===xs?n=at:n===_s||n===$s?n=qt:(n=at,s=void 0);let S=n===at&&o[a+1].startsWith("/>")?" ":"";i+=n===qt?c+Wr:m>=0?(e.push(d),c.slice(0,m)+ws+c.slice(m)+st+S):c+st+(m===-2?a:S);}return [Is(o,i+(o[t]||"<?>")+(r===2?"</svg>":r===3?"</math>":"")),e]},Bt=class o{constructor({strings:r,_$litType$:t},e){let s;this.parts=[];let i=0,n=0,a=r.length-1,c=this.parts,[d,h]=jr(r,t);if(this.el=o.createElement(d,e),lt.currentNode=this.el.content,t===2||t===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes);}for(;(s=lt.nextNode())!==null&&c.length<a;){if(s.nodeType===1){if(s.hasAttributes())for(let m of s.getAttributeNames())if(m.endsWith(ws)){let f=h[n++],S=s.getAttribute(m).split(st),R=/([.?@])?(.*)/.exec(f);c.push({type:1,index:i,name:R[2],strings:S,ctor:R[1]==="."?Le:R[1]==="?"?Me:R[1]==="@"?Ne:gt}),s.removeAttribute(m);}else m.startsWith(st)&&(c.push({type:6,index:i}),s.removeAttribute(m));if(As.test(s.tagName)){let m=s.textContent.split(st),f=m.length-1;if(f>0){s.textContent=ce?ce.emptyScript:"";for(let S=0;S<f;S++)s.append(m[S],Ft()),lt.nextNode(),c.push({type:2,index:++i});s.append(m[f],Ft());}}}else if(s.nodeType===8)if(s.data===Rs)c.push({type:2,index:i});else {let m=-1;for(;(m=s.data.indexOf(st,m+1))!==-1;)c.push({type:7,index:i}),m+=st.length-1;}i++;}}static createElement(r,t){let e=ct.createElement("template");return e.innerHTML=r,e}};function vt(o,r,t=o,e){if(r===z)return r;let s=e!==void 0?t._$Co?.[e]:t._$Cl,i=Gt(r)?void 0:r._$litDirective$;return s?.constructor!==i&&(s?._$AO?.(false),i===void 0?s=void 0:(s=new i(o),s._$AT(o,t,e)),e!==void 0?(t._$Co??(t._$Co=[]))[e]=s:t._$Cl=s),s!==void 0&&(r=vt(o,s._$AS(o,r.values),s,e)),r}var Ce=class{constructor(r,t){this._$AV=[],this._$AN=void 0,this._$AD=r,this._$AM=t;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(r){let{el:{content:t},parts:e}=this._$AD,s=(r?.creationScope??ct).importNode(t,true);lt.currentNode=s;let i=lt.nextNode(),n=0,a=0,c=e[0];for(;c!==void 0;){if(n===c.index){let d;c.type===2?d=new Wt(i,i.nextSibling,this,r):c.type===1?d=new c.ctor(i,c.name,c.strings,this,r):c.type===6&&(d=new ke(i,this,r)),this._$AV.push(d),c=e[++a];}n!==c?.index&&(i=lt.nextNode(),n++);}return lt.currentNode=ct,s}p(r){let t=0;for(let e of this._$AV)e!==void 0&&(e.strings!==void 0?(e._$AI(r,e,t),t+=e.strings.length-2):e._$AI(r[t])),t++;}},Wt=class o{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(r,t,e,s){this.type=2,this._$AH=p,this._$AN=void 0,this._$AA=r,this._$AB=t,this._$AM=e,this.options=s,this._$Cv=s?.isConnected??true;}get parentNode(){let r=this._$AA.parentNode,t=this._$AM;return t!==void 0&&r?.nodeType===11&&(r=t.parentNode),r}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(r,t=this){r=vt(this,r,t),Gt(r)?r===p||r==null||r===""?(this._$AH!==p&&this._$AR(),this._$AH=p):r!==this._$AH&&r!==z&&this._(r):r._$litType$!==void 0?this.$(r):r.nodeType!==void 0?this.T(r):Qr(r)?this.k(r):this._(r);}O(r){return this._$AA.parentNode.insertBefore(r,this._$AB)}T(r){this._$AH!==r&&(this._$AR(),this._$AH=this.O(r));}_(r){this._$AH!==p&&Gt(this._$AH)?this._$AA.nextSibling.data=r:this.T(ct.createTextNode(r)),this._$AH=r;}$(r){let{values:t,_$litType$:e}=r,s=typeof e=="number"?this._$AC(r):(e.el===void 0&&(e.el=Bt.createElement(Is(e.h,e.h[0]),this.options)),e);if(this._$AH?._$AD===s)this._$AH.p(t);else {let i=new Ce(s,this),n=i.u(this.options);i.p(t),this.T(n),this._$AH=i;}}_$AC(r){let t=Ts.get(r.strings);return t===void 0&&Ts.set(r.strings,t=new Bt(r)),t}k(r){Oe(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,e,s=0;for(let i of r)s===t.length?t.push(e=new o(this.O(Ft()),this.O(Ft()),this,this.options)):e=t[s],e._$AI(i),s++;s<t.length&&(this._$AR(e&&e._$AB.nextSibling,s),t.length=s);}_$AR(r=this._$AA.nextSibling,t){for(this._$AP?.(false,true,t);r!==this._$AB;){let e=bs(r).nextSibling;bs(r).remove(),r=e;}}setConnected(r){this._$AM===void 0&&(this._$Cv=r,this._$AP?.(r));}},gt=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(r,t,e,s,i){this.type=1,this._$AH=p,this._$AN=void 0,this.element=r,this.name=t,this._$AM=s,this.options=i,e.length>2||e[0]!==""||e[1]!==""?(this._$AH=Array(e.length-1).fill(new String),this.strings=e):this._$AH=p;}_$AI(r,t=this,e,s){let i=this.strings,n=false;if(i===void 0)r=vt(this,r,t,0),n=!Gt(r)||r!==this._$AH&&r!==z,n&&(this._$AH=r);else {let a=r,c,d;for(r=i[0],c=0;c<i.length-1;c++)d=vt(this,a[e+c],t,c),d===z&&(d=this._$AH[c]),n||(n=!Gt(d)||d!==this._$AH[c]),d===p?r=p:r!==p&&(r+=(d??"")+i[c+1]),this._$AH[c]=d;}n&&!s&&this.j(r);}j(r){r===p?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,r??"");}},Le=class extends gt{constructor(){super(...arguments),this.type=3;}j(r){this.element[this.name]=r===p?void 0:r;}},Me=class extends gt{constructor(){super(...arguments),this.type=4;}j(r){this.element.toggleAttribute(this.name,!!r&&r!==p);}},Ne=class extends gt{constructor(r,t,e,s,i){super(r,t,e,s,i),this.type=5;}_$AI(r,t=this){if((r=vt(this,r,t,0)??p)===z)return;let e=this._$AH,s=r===p&&e!==p||r.capture!==e.capture||r.once!==e.once||r.passive!==e.passive,i=r!==p&&(e===p||s);s&&this.element.removeEventListener(this.name,this,e),i&&this.element.addEventListener(this.name,this,r),this._$AH=r;}handleEvent(r){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,r):this._$AH.handleEvent(r);}},ke=class{constructor(r,t,e){this.element=r,this.type=6,this._$AN=void 0,this._$AM=t,this.options=e;}get _$AU(){return this._$AM._$AU}_$AI(r){vt(this,r);}};var Yr=Ut.litHtmlPolyfillSupport;Yr?.(Bt,Wt),(Ut.litHtmlVersions??(Ut.litHtmlVersions=[])).push("3.3.2");var Cs=(o,r,t)=>{let e=t?.renderBefore??r,s=e._$litPart$;if(s===void 0){let i=t?.renderBefore??null;e._$litPart$=s=new Wt(r.insertBefore(Ft(),i),i,void 0,t??{});}return s._$AI(o),s};var Qt=globalThis,b=class extends K{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){var t;let r=super.createRenderRoot();return (t=this.renderOptions).renderBefore??(t.renderBefore=r.firstChild),r}update(r){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(r),this._$Do=Cs(t,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return z}};b._$litElement$=true,b.finalized=true,Qt.litElementHydrateSupport?.({LitElement:b});var Vr=Qt.litElementPolyfillSupport;Vr?.({LitElement:b});(Qt.litElementVersions??(Qt.litElementVersions=[])).push("4.2.2");var $=o=>(r,t)=>{t!==void 0?t.addInitializer(()=>{customElements.define(o,r);}):customElements.define(o,r);};var Xr={attribute:true,type:String,converter:Ht,reflect:false,hasChanged:le},Kr=(o=Xr,r,t)=>{let{kind:e,metadata:s}=t,i=globalThis.litPropertyMetadata.get(s);if(i===void 0&&globalThis.litPropertyMetadata.set(s,i=new Map),e==="setter"&&((o=Object.create(o)).wrapped=true),i.set(t.name,o),e==="accessor"){let{name:n}=t;return {set(a){let c=r.get.call(this);r.set.call(this,a),this.requestUpdate(n,c,o,true,a);},init(a){return a!==void 0&&this.C(n,void 0,o,a),a}}}if(e==="setter"){let{name:n}=t;return function(a){let c=this[n];r.call(this,a),this.requestUpdate(n,c,o,true,a);}}throw Error("Unsupported decorator location: "+e)};function L(o){return (r,t)=>typeof t=="object"?Kr(o,r,t):((e,s,i)=>{let n=s.hasOwnProperty(i);return s.constructor.createProperty(i,e),n?Object.getOwnPropertyDescriptor(s,i):void 0})(o,r,t)}function E(o){return L({...o,state:true,attribute:false})}var jt=class extends b{constructor(){super(...arguments);this.method="";}createRenderRoot(){return this}render(){let t=this.method.toUpperCase();return l`<span class="method-badge method-badge-${t}">${t}</span>`}};u([L()],jt.prototype,"method",2),jt=u([$("bk-method-badge")],jt);var q="/__brakit/api",U="/__brakit",T={flows:`${q}/flows`,requests:`${q}/requests`,events:`${q}/events`,clear:`${q}/clear`,fetches:`${q}/fetches`,errors:`${q}/errors`,logs:`${q}/logs`,queries:`${q}/queries`,metricsLive:`${q}/metrics/live`,insights:`${q}/insights`,tab:`${q}/tab`,activity:`${q}/activity`,graph:`${q}/graph`};var Et="polling",pe="static",zr="auth-handshake",Jr="auth-check",Zr="middleware",Yt={[zr]:1,[Jr]:1,[Zr]:1};var he="fetch";var ue="error_event",me="query",fe="issues";var Pe={overview:"Overview",actions:"Actions",insights:"Insights",performance:"Performance",graph:"Graph",explorer:"Explorer",requests:"Requests",fetches:"Server Fetches",queries:"Queries",errors:"Errors",logs:"Logs",security:"Security"},He={overview:"Live summary of your application",actions:"User actions captured as sequences of HTTP requests",insights:"Security findings, error patterns, and issue tracking",performance:"Endpoint health and response time trends",graph:"Runtime dependency graph of your application",explorer:"Browse raw requests, fetches, queries, logs, and errors",requests:"All HTTP requests proxied through brakit",fetches:"Outbound HTTP calls made by your server to external services",queries:"Database queries executed during request handling",errors:"Unhandled exceptions and errors thrown by your application",logs:"Console output from your application",security:"Security findings and recommendations"},ve=[{key:"requests",label:"Requests"},{key:"fetches",label:"Fetches"},{key:"queries",label:"Queries"},{key:"logs",label:"Logs"},{key:"errors",label:"Errors"}];var Ls=new Set(["exposed-secret","token-in-url","stack-trace-leak","error-info-leak","insecure-cookie","sensitive-logs","cors-credentials","response-pii-leak"]),Ms=new Set(["slow-endpoint","n1","high-query-count"]);function Vt(o){let r=o.issue.rule||o.issue.type||"";return Ls.has(r)?"security":Ms.has(r)?"performance":"quality"}var O={CLEAR_CONFIRM:"This will clear all data including performance metrics history. Continue?",CLEARED_TOAST:"Cleared",EMPTY_TITLE:"Waiting for requests...",EMPTY_SUBTITLE_OVERVIEW:"Start using your app to see your dashboard here",ALL_CLEAR:"All clear",ALL_CLEAR_DETAIL:"No issues detected",NO_ACTIONS:"No actions captured yet",NO_REQUEST_DATA:"No request data yet",ALL_FAST:"All fast",NO_SLOW_ENDPOINTS:"No slow endpoints detected",ZERO_ERRORS:"0 errors",ALL_REQUESTS_OK:"All requests successful",BUILD_GRAPH:"Navigate your app to build the graph",REVIEW_RECOMMENDED:"review recommended"};var Fe=100,yt=300,_t=800,Ge=2e3,Be=100,We=50,Qe=500;var rt="__all__",Ee={SELECT:"var(--blue)",INSERT:"var(--green)",UPDATE:"var(--amber)",DELETE:"var(--red)",COUNT:"var(--text-muted)"},qs={error:"var(--red)",warn:"var(--amber)",info:"var(--blue)",debug:"var(--text-muted)",log:"var(--text-dim)"},Ve=["#2563eb","#7c3aed","#16a34a","#d97706","#dc2626","#0891b2","#ea580c","#c026d3","#059669","#db2777"],Xt={green:"#4ade80",amber:"#fbbf24",red:"#f87171"},xt=[{max:Fe,label:"Fast",color:"var(--green)",bg:"var(--green-bg)",border:"var(--green-border)"},{max:yt,label:"Good",color:"var(--green)",bg:"var(--green-bg-subtle)",border:"var(--green-border-subtle)"},{max:_t,label:"OK",color:"var(--amber)",bg:"var(--amber-bg)",border:"var(--amber-border)"},{max:Ge,label:"Slow",color:"var(--red)",bg:"var(--red-bg)",border:"var(--red-border)"},{max:1/0,label:"Critical",color:"var(--red)",bg:"var(--red-bg)",border:"var(--red-border)"}],Us="rgba(228,228,231,0.8)",Xe="rgba(113,113,122,0.7)",Fs="10px monospace",Ke="9px monospace";var Gs={top:16,right:16,bottom:28,left:52},Bs={fetch:"var(--blue)",log:"var(--text-muted)",error:"var(--red)",query:"var(--accent)"},Ws={fetch:"FETCH",log:"LOG",error:"ERROR",query:"QUERY"},Qs=new Set(["cookie","set-cookie","authorization","proxy-authorization","x-api-key","x-auth-token"]),js={400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",408:"Timeout",409:"Conflict",422:"Unprocessable",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},Ys=new Set(["host","connection","accept-encoding"]),Y={critical:{icon:"\u2717",cls:"critical",sort:0},warning:{icon:"\u26A0",cls:"warning",sort:1},info:{icon:"\u2139",cls:"info",sort:2}};function y(o){return o<1e3?o+"ms":(o/1e3).toFixed(1)+"s"}function ot(o){return !o||o===0?"":o<1024?o+"b":(o/1024).toFixed(1)+"kb"}function it(o){return o?o.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',"""):""}function St(o){return o>=500?"status-pill-5xx":o>=400?"status-pill-4xx":o>=300?"status-pill-3xx":"status-pill-2xx"}function Vs(o){return js[o]||(o>=500?"Server Error":o>=400?"Client Error":"OK")}function ti(o,r){if(Qs.has(o.toLowerCase())){let t=String(r);return t.length<=8?"****":t.slice(0,4)+"..."+t.slice(-4)+" ("+t.length+" chars)"}return String(r)}function Tt(o){return !o||Object.keys(o).length===0?'<span style="color:var(--text-muted)">No headers</span>':Object.entries(o).map(([r,t])=>'<span class="json-key">'+it(r)+"</span>: "+it(ti(r,t))).join(`
|
|
898
|
+
`)}function dt(o){if(!o)return '<span style="color:var(--text-muted)">No body</span>';try{let r=JSON.parse(o);return ei(JSON.stringify(r,null,2))}catch{return it(o)}}function ei(o){return it(o).replace(/("(?:[^"\\]|\\.)*")(\s*:)?|\b(true|false)\b|\bnull\b|(-?\d+\.?\d*(?:[eE][+-]?\d+)?)/g,(r,t,e,s,i)=>t?e?'<span class="json-key">'+t+"</span>"+e:'<span class="json-str">'+t+"</span>":s?'<span class="json-bool">'+r+"</span>":i?'<span class="json-num">'+r+"</span>":r==="null"?'<span class="json-null">null</span>':r)}var Kt=class extends b{constructor(){super(...arguments);this.code=0;}createRenderRoot(){return this}render(){let t=St(this.code);return l`<span class="status-pill ${t}">${this.code}</span>`}};u([L({type:Number})],Kt.prototype,"code",2),Kt=u([$("bk-status-pill")],Kt);var zt=class extends b{constructor(){super(...arguments);this.ms=0;}createRenderRoot(){return this}render(){return l`<span class="req-duration">${y(this.ms)}</span>`}};u([L({type:Number})],zt.prototype,"ms",2),zt=u([$("bk-duration-label")],zt);var wt=class extends b{constructor(){super(...arguments);this.title="";this.subtitle="";}createRenderRoot(){return this}render(){return l`
|
|
755
899
|
<div class="empty">
|
|
756
900
|
<span class="empty-title">${this.title}</span>
|
|
757
901
|
<span class="empty-sub">${this.subtitle}</span>
|
|
758
902
|
</div>
|
|
759
|
-
`}};u([
|
|
903
|
+
`}};u([L()],wt.prototype,"title",2),u([L()],wt.prototype,"subtitle",2),wt=u([$("bk-empty-state")],wt);var D=class extends b{constructor(){super(...arguments);this.message="";this.visible=false;}createRenderRoot(){return this}static show(t){let e=document.querySelector("bk-toast");e&&e.showMessage(t);}showMessage(t){this.hideTimer&&clearTimeout(this.hideTimer),this.message=t,this.visible=true,this.hideTimer=setTimeout(()=>{this.visible=false;},2e3);}render(){return l`<div class="toast ${this.visible?"show":""}">${this.message}</div>`}};u([E()],D.prototype,"message",2),u([E()],D.prototype,"visible",2),D=u([$("bk-toast")],D);var pt=class extends b{constructor(){super(...arguments);this.text="";this.label="Copy";this.toastMessage="Copied";}createRenderRoot(){return this}async copy(t){t.stopPropagation();try{await navigator.clipboard.writeText(this.text),D.show(this.toastMessage);}catch{}}render(){return l`<button class="query-detail-copy" @click=${this.copy}>${this.label}</button>`}};u([L()],pt.prototype,"text",2),u([L()],pt.prototype,"label",2),u([L({attribute:"toast-message"})],pt.prototype,"toastMessage",2),pt=u([$("bk-copy-button")],pt);var ht=class extends b{constructor(){super(...arguments);this.value="";this.label="";this.color="";}createRenderRoot(){return this}render(){return l`
|
|
760
904
|
<div class="fetch-stat">
|
|
761
905
|
<span class="fetch-stat-value" style="color:${this.color}">${this.value}</span>
|
|
762
906
|
<span class="fetch-stat-label">${this.label}</span>
|
|
763
907
|
</div>
|
|
764
|
-
`}};u([
|
|
908
|
+
`}};u([L()],ht.prototype,"value",2),u([L()],ht.prototype,"label",2),u([L()],ht.prototype,"color",2),ht=u([$("bk-stat-card")],ht);var nt=class extends Event{constructor(r,t,e,s){super("context-request",{bubbles:true,composed:true}),this.context=r,this.contextTarget=t,this.callback=e,this.subscribe=s??false;}};var Rt=class{constructor(r,t,e,s){if(this.subscribe=false,this.provided=false,this.value=void 0,this.t=(i,n)=>{this.unsubscribe&&(this.unsubscribe!==n&&(this.provided=false,this.unsubscribe()),this.subscribe||this.unsubscribe()),this.value=i,this.host.requestUpdate(),this.provided&&!this.subscribe||(this.provided=true,this.callback&&this.callback(i,n)),this.unsubscribe=n;},this.host=r,t.context!==void 0){let i=t;this.context=i.context,this.callback=i.callback,this.subscribe=i.subscribe??false;}else this.context=t,this.callback=e,this.subscribe=s??false;this.host.addController(this);}hostConnected(){this.dispatchRequest();}hostDisconnected(){this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=void 0);}dispatchRequest(){this.host.dispatchEvent(new nt(this.context,this.host,this.t,this.subscribe));}};var be=class{get value(){return this.o}set value(r){this.setValue(r);}setValue(r,t=false){let e=t||!Object.is(r,this.o);this.o=r,e&&this.updateObservers();}constructor(r){this.subscriptions=new Map,this.updateObservers=()=>{for(let[t,{disposer:e}]of this.subscriptions)t(this.o,e);},r!==void 0&&(this.value=r);}addCallback(r,t,e){if(!e)return void r(this.value);this.subscriptions.has(r)||this.subscriptions.set(r,{disposer:()=>{this.subscriptions.delete(r);},consumerHost:t});let{disposer:s}=this.subscriptions.get(r);r(this.value,s);}clearCallbacks(){this.subscriptions.clear();}};var ze=class extends Event{constructor(r,t){super("context-provider",{bubbles:true,composed:true}),this.context=r,this.contextTarget=t;}},At=class extends be{constructor(r,t,e){super(t.context!==void 0?t.initialValue:e),this.onContextRequest=s=>{if(s.context!==this.context)return;let i=s.contextTarget??s.composedPath()[0];i!==this.host&&(s.stopPropagation(),this.addCallback(s.callback,i,s.subscribe));},this.onProviderRequest=s=>{if(s.context!==this.context||(s.contextTarget??s.composedPath()[0])===this.host)return;let i=new Set;for(let[n,{consumerHost:a}]of this.subscriptions)i.has(n)||(i.add(n),a.dispatchEvent(new nt(this.context,a,n,true)));s.stopPropagation();},this.host=r,t.context!==void 0?this.context=t.context:this.context=t,this.attachListeners(),this.host.addController?.(this);}attachListeners(){this.host.addEventListener("context-request",this.onContextRequest),this.host.addEventListener("context-provider",this.onProviderRequest);}hostConnected(){this.host.dispatchEvent(new ze(this.context,this.host));}};function Je({context:o}){return (r,t)=>{let e=new WeakMap;if(typeof t=="object")return {get(){return r.get.call(this)},set(s){return e.get(this).setValue(s),r.set.call(this,s)},init(s){return e.set(this,new At(this,{context:o,initialValue:s})),s}};{r.constructor.addInitializer((n=>{e.set(n,new At(n,{context:o}));}));let s=Object.getOwnPropertyDescriptor(r,t),i;if(s===void 0){let n=new WeakMap;i={get(){return n.get(this)},set(a){e.get(this).setValue(a),n.set(this,a);},configurable:true,enumerable:true};}else {let n=s.set;i={...s,set(a){e.get(this).setValue(a),n?.call(this,a);}};}return void Object.defineProperty(r,t,i)}}}function I({context:o,subscribe:r}){return (t,e)=>{typeof e=="object"?e.addInitializer((function(){new Rt(this,{context:o,callback:s=>{t.set.call(this,s);},subscribe:r});})):t.constructor.addInitializer((s=>{new Rt(s,{context:o,callback:i=>{s[e]=i;},subscribe:r});}));}}var A="dashboard-store",ye=class extends EventTarget{constructor(){super(...arguments);this._state={flows:[],requests:[],fetches:[],errors:[],logs:[],queries:[],issues:[],metrics:[],viewMode:"simple",activeView:"overview"};}get state(){return this._state}setState(t,e){this._state={...this._state,[t]:e},this.notify(t);}setFlows(t){this.setState("flows",t);}setRequests(t){this.setState("requests",t);}setFetches(t){this.setState("fetches",t);}setErrors(t){this.setState("errors",t);}setLogs(t){this.setState("logs",t);}setQueries(t){this.setState("queries",t);}setIssues(t){this.setState("issues",t);}setMetrics(t){this.setState("metrics",t);}prependRequest(t){let e=[t,...this._state.requests.slice(0,999)];this._state={...this._state,requests:e},this.notify("requests");}prependFetch(t){let e=[t,...this._state.fetches.slice(0,999)];this._state={...this._state,fetches:e},this.notify("fetches");}prependError(t){let e=[t,...this._state.errors.slice(0,999)];this._state={...this._state,errors:e},this.notify("errors");}prependLog(t){let e=[t,...this._state.logs.slice(0,999)];this._state={...this._state,logs:e},this.notify("logs");}prependQuery(t){let e=[t,...this._state.queries.slice(0,999)];this._state={...this._state,queries:e},this.notify("queries");}setActiveView(t){this.setState("activeView",t);}setViewMode(t){this.setState("viewMode",t);}clearAll(){this._state={...this._state,flows:[],requests:[],fetches:[],errors:[],logs:[],queries:[],issues:[],metrics:[]},this.notify("all");}notify(t){this.dispatchEvent(new CustomEvent("state-changed",{detail:t}));}};var It=class extends b{constructor(){super(...arguments);this.expandedIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}toggleError(t){this.expandedIdx=this.expandedIdx===t?-1:t;}renderErrorRow(t,e){let s=new Date(t.timestamp).toLocaleTimeString(),i=this.expandedIdx===e;return l`
|
|
765
909
|
<div
|
|
766
|
-
class="req-row tel-clickable ${
|
|
767
|
-
@click=${()=>this.toggleError(
|
|
910
|
+
class="req-row tel-clickable ${i?"expanded":""}"
|
|
911
|
+
@click=${()=>this.toggleError(e)}
|
|
768
912
|
>
|
|
769
913
|
<span class="tel-error-name" title=${t.name}>${t.name}</span>
|
|
770
914
|
<span class="tel-message" title=${t.message}>${t.message}</span>
|
|
771
|
-
<span class="tel-timestamp">${
|
|
915
|
+
<span class="tel-timestamp">${s}</span>
|
|
772
916
|
</div>
|
|
773
|
-
${
|
|
774
|
-
`}render(){let t=this.store.state.errors;return t.length===0?
|
|
917
|
+
${i&&t.stack?l`<div class="error-stack">${t.stack}</div>`:p}
|
|
918
|
+
`}render(){let t=this.store.state.errors;return t.length===0?l`<bk-empty-state
|
|
775
919
|
title="No errors"
|
|
776
920
|
subtitle="No errors have been captured yet"
|
|
777
|
-
></bk-empty-state>`:
|
|
921
|
+
></bk-empty-state>`:l`
|
|
778
922
|
<div class="col-header">
|
|
779
923
|
<span style="width:180px">Type</span>
|
|
780
924
|
<span style="flex:1">Message</span>
|
|
781
925
|
<span style="width:130px;text-align:right">Time</span>
|
|
782
926
|
</div>
|
|
783
927
|
<div id="error-list">
|
|
784
|
-
${t.map((s
|
|
928
|
+
${t.map((e,s)=>this.renderErrorRow(e,s))}
|
|
785
929
|
</div>
|
|
786
|
-
`}};u([
|
|
930
|
+
`}};u([I({context:A})],It.prototype,"store",2),u([E()],It.prototype,"expandedIdx",2),It=u([$("bk-errors-view")],It);var Jt=class extends b{createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}renderAnalysis(r){if(r.length===0)return p;let t={error:0,warn:0,info:0,debug:0,log:0};for(let e of r)t[e.level]!==void 0&&t[e.level]++;return l`
|
|
787
931
|
<div id="log-analysis">
|
|
788
932
|
<div class="fetch-summary">
|
|
789
|
-
<bk-stat-card value=${String(
|
|
790
|
-
${t.error>0?
|
|
791
|
-
${t.warn>0?
|
|
933
|
+
<bk-stat-card value=${String(r.length)} label="Total Logs"></bk-stat-card>
|
|
934
|
+
${t.error>0?l`<bk-stat-card value=${String(t.error)} label="Errors" color="var(--red)"></bk-stat-card>`:p}
|
|
935
|
+
${t.warn>0?l`<bk-stat-card value=${String(t.warn)} label="Warnings" color="var(--amber)"></bk-stat-card>`:p}
|
|
792
936
|
<bk-stat-card value=${String(t.info)} label="Info"></bk-stat-card>
|
|
793
|
-
${t.debug>0?
|
|
794
|
-
${t.log>0?
|
|
937
|
+
${t.debug>0?l`<bk-stat-card value=${String(t.debug)} label="Debug"></bk-stat-card>`:p}
|
|
938
|
+
${t.log>0?l`<bk-stat-card value=${String(t.log)} label="Log"></bk-stat-card>`:p}
|
|
795
939
|
</div>
|
|
796
940
|
</div>
|
|
797
|
-
`}renderLogRow(
|
|
941
|
+
`}renderLogRow(r){let t=new Date(r.timestamp).toLocaleTimeString();return l`
|
|
798
942
|
<div class="req-row">
|
|
799
|
-
<span class="tel-level tel-level-${
|
|
800
|
-
<span class="tel-message tel-mono" title=${
|
|
943
|
+
<span class="tel-level tel-level-${r.level}">${r.level.toUpperCase()}</span>
|
|
944
|
+
<span class="tel-message tel-mono" title=${r.message}>${r.message}</span>
|
|
801
945
|
<span class="tel-timestamp">${t}</span>
|
|
802
946
|
</div>
|
|
803
|
-
`}render(){let
|
|
947
|
+
`}render(){let r=this.store.state.logs;return r.length===0?l`<bk-empty-state
|
|
804
948
|
title="No logs"
|
|
805
949
|
subtitle="No console output has been captured yet"
|
|
806
|
-
></bk-empty-state>`:
|
|
807
|
-
${this.renderAnalysis(
|
|
950
|
+
></bk-empty-state>`:l`
|
|
951
|
+
${this.renderAnalysis(r)}
|
|
808
952
|
<div class="col-header">
|
|
809
953
|
<span style="width:52px">Level</span>
|
|
810
954
|
<span style="flex:1">Message</span>
|
|
811
955
|
<span style="width:130px;text-align:right">Time</span>
|
|
812
956
|
</div>
|
|
813
957
|
<div id="log-list">
|
|
814
|
-
${
|
|
958
|
+
${r.map(t=>this.renderLogRow(t))}
|
|
815
959
|
</div>
|
|
816
|
-
`}};u([
|
|
960
|
+
`}};u([I({context:A})],Jt.prototype,"store",2),Jt=u([$("bk-logs-view")],Jt);var Xs={CHILD:2},Ks=o=>(...r)=>({_$litDirective$:o,values:r}),_e=class{constructor(r){}get _$AU(){return this._$AM._$AU}_$AT(r,t,e){this._$Ct=r,this._$AM=t,this._$Ci=e;}_$AS(r,t){return this.update(r,t)}update(r,t){return this.render(...t)}};var Zt=class extends _e{constructor(r){if(super(r),this.it=p,r.type!==Xs.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===p||r==null)return this._t=void 0,this.it=r;if(r===z)return r;if(typeof r!="string")throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;let t=[r];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}};Zt.directiveName="unsafeHTML",Zt.resultType=1;var G=Ks(Zt);var ri=new Set(["SELECT","FROM","WHERE","AND","OR","INSERT","INTO","VALUES","UPDATE","SET","DELETE","JOIN","LEFT","RIGHT","INNER","OUTER","ON","GROUP","BY","ORDER","HAVING","LIMIT","OFFSET","AS","IN","NOT","NULL","IS","LIKE","BETWEEN","EXISTS","CASE","WHEN","THEN","ELSE","END","COUNT","SUM","AVG","MIN","MAX","DISTINCT","UNION","ALL","CREATE","TABLE","ALTER","DROP","INDEX","RETURNING","WITH","RECURSIVE","OVER","PARTITION","WINDOW","FETCH","NEXT","ROWS","ONLY","CAST","COALESCE","NULLIF","EXTRACT","INTERVAL","TRUE","FALSE","ASC","DESC","USING","NATURAL","CROSS","FULL","ROLLBACK","COMMIT","BEGIN","TRANSACTION","SAVEPOINT","RELEASE"]);function zs(o){let r=o.trimStart().split(/\s/)[0];return r?r.toUpperCase():"?"}function Js(o){let r=o.replace(/\s+/g," ").trim(),t=r.match(/\bFROM\s+["'`]?(\w+)["'`]?/i);if(t)return t[1];let e=r.match(/\bINTO\s+["'`]?(\w+)["'`]?/i);if(e)return e[1];let s=r.match(/\bUPDATE\s+["'`]?(\w+)["'`]?/i);return s?s[1]:""}function Zs(o){return it(o).replace(/\b\w+\b/g,r=>ri.has(r.toUpperCase())?'<span class="sql-kw">'+r+"</span>":r)}var Ct=class extends b{constructor(){super(...arguments);this.expandedIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}toggleQuery(t){this.expandedIdx=this.expandedIdx===t?-1:t;}queryDuration(t){return t===0?"<1ms":y(t)}getQueryInfo(t){let e=(t.normalizedOp||t.operation||(t.sql?zs(t.sql):"?")).toUpperCase(),s=t.table||t.model||(t.sql?Js(t.sql):""),i=t.sql||e+" "+s;return {op:e,table:s,sqlText:i}}renderQueryRow(t,e){let{op:s,table:i,sqlText:n}=this.getQueryInfo(t),a=Ee[s]||"var(--text-dim)",c=t.durationMs>Be,d=t.sql||s+" "+i,h=this.expandedIdx===e;return l`
|
|
817
961
|
<div>
|
|
818
962
|
<div
|
|
819
963
|
class="req-row query-row tel-clickable ${h?"expanded":""}"
|
|
820
|
-
@click=${()=>this.toggleQuery(
|
|
964
|
+
@click=${()=>this.toggleQuery(e)}
|
|
821
965
|
>
|
|
822
|
-
<span class="query-op" title=${
|
|
823
|
-
<span class="query-table" title=${
|
|
824
|
-
<span class="query-preview" title=${
|
|
966
|
+
<span class="query-op" title=${s} style="color:${a}">${s}</span>
|
|
967
|
+
<span class="query-table" title=${i}>${i}</span>
|
|
968
|
+
<span class="query-preview" title=${d}>${d}</span>
|
|
825
969
|
<span class="query-dur${c?" query-slow":""}">${this.queryDuration(t.durationMs)}</span>
|
|
826
970
|
</div>
|
|
827
971
|
<div class="query-detail ${h?"open":""}">
|
|
828
|
-
${h?
|
|
829
|
-
<pre class="query-detail-sql"
|
|
972
|
+
${h?l`
|
|
973
|
+
<pre class="query-detail-sql">${G(Zs(n))}</pre>
|
|
830
974
|
<bk-copy-button .text=${n} label="Copy"></bk-copy-button>
|
|
831
|
-
`:
|
|
975
|
+
`:p}
|
|
832
976
|
</div>
|
|
833
977
|
</div>
|
|
834
|
-
`}render(){let t=this.store.state.queries;return t.length===0?
|
|
978
|
+
`}render(){let t=this.store.state.queries;return t.length===0?l`<bk-empty-state
|
|
835
979
|
title="No queries"
|
|
836
980
|
subtitle="No database queries have been captured yet"
|
|
837
|
-
></bk-empty-state>`:
|
|
981
|
+
></bk-empty-state>`:l`
|
|
838
982
|
<div class="col-header">
|
|
839
983
|
<span style="width:70px;border-right:1px solid var(--border);padding-right:16px">Operation</span>
|
|
840
984
|
<span style="width:170px;border-right:1px solid var(--border);padding-right:16px">Table</span>
|
|
@@ -842,37 +986,37 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
842
986
|
<span style="width:60px;text-align:right">Time</span>
|
|
843
987
|
</div>
|
|
844
988
|
<div id="query-list">
|
|
845
|
-
${t.map((s
|
|
989
|
+
${t.map((e,s)=>this.renderQueryRow(e,s))}
|
|
846
990
|
</div>
|
|
847
|
-
`}};u([
|
|
991
|
+
`}};u([I({context:A})],Ct.prototype,"store",2),u([E()],Ct.prototype,"expandedIdx",2),Ct=u([$("bk-queries-view")],Ct);function Ze(o){return o.replaceAll("'","'\\''")}function ii(o,r){let t=Object.entries(o.headers||{}).filter(([i])=>!Ys.has(i)).map(([i,n])=>`-H '${Ze(i)}: ${Ze(n)}'`).join(" "),e=o.requestBody?` -d '${Ze(o.requestBody)}'`:"",s=r?`http://localhost:${r}`:"";return `curl -X ${o.method} ${t}${e} '${s}${o.url}'`}function Lt(o){let r=window.__BRAKIT_CONFIG__?.port??"",t=ii(o,r);navigator.clipboard.writeText(t).then(()=>D.show("Copied cURL command"));}var Mt=class extends b{constructor(){super(...arguments);this.expandedId=null;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}toggleRequest(t){this.expandedId=this.expandedId===t?null:t;}handleCopyAsCurl(t,e){e.stopPropagation(),Lt(t);}renderDetail(t){return l`
|
|
848
992
|
<div class="detail-meta">
|
|
849
993
|
<span><bk-method-badge .method=${t.method}></bk-method-badge> ${t.url}</span>
|
|
850
994
|
<span><bk-status-pill .code=${t.statusCode}></bk-status-pill></span>
|
|
851
995
|
<span>${t.durationMs}ms</span>
|
|
852
|
-
${t.responseSize?
|
|
996
|
+
${t.responseSize?l`<span>${ot(t.responseSize)}</span>`:p}
|
|
853
997
|
</div>
|
|
854
998
|
<div class="request-timeline tl-hidden" data-request-id=${t.id} data-request-started=${String(t.startedAt)}></div>
|
|
855
999
|
<div class="detail-grid">
|
|
856
|
-
<div class="detail-section"><h4>Request Headers</h4><pre
|
|
857
|
-
<div class="detail-section"><h4>Response Headers</h4><pre
|
|
858
|
-
<div class="detail-section"><h4>Request Body</h4><pre
|
|
859
|
-
<div class="detail-section"><h4>Response Body</h4><pre
|
|
1000
|
+
<div class="detail-section"><h4>Request Headers</h4><pre>${G(Tt(t.headers))}</pre></div>
|
|
1001
|
+
<div class="detail-section"><h4>Response Headers</h4><pre>${G(Tt(t.responseHeaders))}</pre></div>
|
|
1002
|
+
<div class="detail-section"><h4>Request Body</h4><pre>${G(dt(t.requestBody))}</pre></div>
|
|
1003
|
+
<div class="detail-section"><h4>Response Body</h4><pre>${G(dt(t.responseBody))}</pre></div>
|
|
860
1004
|
</div>
|
|
861
1005
|
<div class="detail-actions">
|
|
862
|
-
<button class="btn btn-curl" @click=${
|
|
1006
|
+
<button class="btn btn-curl" @click=${e=>this.handleCopyAsCurl(t,e)}>Copy cURL</button>
|
|
863
1007
|
</div>
|
|
864
|
-
`}renderRequestRow(t){let
|
|
865
|
-
<div class="req-row ${
|
|
1008
|
+
`}renderRequestRow(t){let e=this.expandedId===t.id;return l`
|
|
1009
|
+
<div class="req-row ${e?"expanded":""}" @click=${()=>this.toggleRequest(t.id)}>
|
|
866
1010
|
<div class="req-summary">
|
|
867
1011
|
<bk-method-badge .method=${t.method}></bk-method-badge>
|
|
868
1012
|
<span class="req-url">${t.url}</span>
|
|
869
1013
|
<bk-status-pill .code=${t.statusCode}></bk-status-pill>
|
|
870
1014
|
<bk-duration-label .ms=${t.durationMs}></bk-duration-label>
|
|
871
|
-
<span class="req-size">${
|
|
1015
|
+
<span class="req-size">${ot(t.responseSize)}</span>
|
|
872
1016
|
</div>
|
|
873
1017
|
</div>
|
|
874
|
-
<div class="req-detail ${
|
|
875
|
-
`}render(){let t=this.store.state.requests.filter(
|
|
1018
|
+
<div class="req-detail ${e?"open":""}">${e?this.renderDetail(t):p}</div>
|
|
1019
|
+
`}render(){let t=this.store.state.requests.filter(e=>!e.path?.startsWith(U));return t.length===0?l`<bk-empty-state title="No requests" subtitle="No HTTP requests have been captured yet"></bk-empty-state>`:l`
|
|
876
1020
|
<div class="col-header">
|
|
877
1021
|
<span style="width:60px">Method</span>
|
|
878
1022
|
<span style="flex:1">URL</span>
|
|
@@ -880,54 +1024,54 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
880
1024
|
<span style="width:70px;text-align:right">Time</span>
|
|
881
1025
|
<span style="width:60px;text-align:right">Size</span>
|
|
882
1026
|
</div>
|
|
883
|
-
<div id="request-list">${t.map(
|
|
884
|
-
`}};u([
|
|
1027
|
+
<div id="request-list">${t.map(e=>this.renderRequestRow(e))}</div>
|
|
1028
|
+
`}};u([I({context:A})],Mt.prototype,"store",2),u([E()],Mt.prototype,"expandedId",2),Mt=u([$("bk-requests-view")],Mt);var te=class extends b{createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}buildGroups(r,t){let e=new Map;for(let i of t)e.set(i.id,i);let s={};for(let i of r){let n=i.method+" "+i.url;s[n]||(s[n]={method:i.method,url:i.url,count:0,totalDur:0,maxDur:0,errors:0,callers:{},statusCodes:{},firstTs:i.timestamp,lastTs:i.timestamp});let a=s[n];if(a.count++,a.totalDur+=i.durationMs,i.durationMs>a.maxDur&&(a.maxDur=i.durationMs),i.statusCode>=400&&a.errors++,a.statusCodes[i.statusCode]=(a.statusCodes[i.statusCode]||0)+1,i.timestamp<a.firstTs&&(a.firstTs=i.timestamp),i.timestamp>a.lastTs&&(a.lastTs=i.timestamp),i.parentRequestId){let c=e.get(i.parentRequestId);c&&(a.callers[c.method+" "+(c.path||c.url)]=1);}}return Object.values(s).sort((i,n)=>n.count-i.count)}renderSummary(r){let t=new Set,e=0,s=0;for(let n of r)t.add(n.url),n.statusCode>=400&&e++,s+=n.durationMs;let i=Math.round(s/r.length);return l`
|
|
885
1029
|
<div class="fetch-summary">
|
|
886
|
-
<bk-stat-card value=${String(
|
|
1030
|
+
<bk-stat-card value=${String(r.length)} label="Total Fetches"></bk-stat-card>
|
|
887
1031
|
<bk-stat-card value=${String(t.size)} label="Unique URLs"></bk-stat-card>
|
|
888
|
-
<bk-stat-card value=${String(
|
|
889
|
-
<bk-stat-card value=${
|
|
1032
|
+
<bk-stat-card value=${String(e)} label="Errors" color=${e>0?"var(--red)":""}></bk-stat-card>
|
|
1033
|
+
<bk-stat-card value=${y(i)} label="Avg Duration"></bk-stat-card>
|
|
890
1034
|
</div>
|
|
891
|
-
`}formatTime(
|
|
1035
|
+
`}formatTime(r){return new Date(r).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})}renderGroup(r){let t=Math.round(r.totalDur/r.count),e=r.count>0?Math.round(r.errors/r.count*100):0,s=Object.keys(r.callers),i=Object.entries(r.statusCodes),n=i.length>0?Number(i.sort((a,c)=>c[1]-a[1])[0][0]):0;return l`
|
|
892
1036
|
<div class="fetch-group">
|
|
893
1037
|
<div class="fetch-group-header">
|
|
894
|
-
<bk-method-badge .method=${
|
|
895
|
-
<span class="fetch-group-url" title=${
|
|
896
|
-
${n>0?
|
|
897
|
-
<span class="fetch-group-count">${
|
|
1038
|
+
<bk-method-badge .method=${r.method}></bk-method-badge>
|
|
1039
|
+
<span class="fetch-group-url" title=${r.url}>${r.url}</span>
|
|
1040
|
+
${n>0?l`<bk-status-pill .code=${n}></bk-status-pill>`:p}
|
|
1041
|
+
<span class="fetch-group-count">${r.count}x</span>
|
|
898
1042
|
</div>
|
|
899
1043
|
<div class="fetch-group-meta">
|
|
900
|
-
<span>avg ${
|
|
1044
|
+
<span>avg ${y(t)}</span>
|
|
901
1045
|
<span class="fetch-group-sep">\u00b7</span>
|
|
902
|
-
<span>max ${
|
|
1046
|
+
<span>max ${y(r.maxDur)}</span>
|
|
903
1047
|
<span class="fetch-group-sep">\u00b7</span>
|
|
904
|
-
${
|
|
1048
|
+
${e>0?l`<span class="fetch-group-err">${e}% errors</span>`:l`<span class="fetch-group-ok">0% errors</span>`}
|
|
905
1049
|
</div>
|
|
906
|
-
${
|
|
1050
|
+
${r.firstTs>0?l`
|
|
907
1051
|
<div class="fetch-group-timeline">
|
|
908
1052
|
<span class="fetch-group-timeline-dot"></span>
|
|
909
1053
|
<span class="fetch-group-timeline-range">
|
|
910
|
-
${this.formatTime(
|
|
1054
|
+
${this.formatTime(r.firstTs)}${r.firstTs!==r.lastTs?l` \u2192 ${this.formatTime(r.lastTs)}`:p}
|
|
911
1055
|
</span>
|
|
912
|
-
</div>`:
|
|
913
|
-
${
|
|
1056
|
+
</div>`:p}
|
|
1057
|
+
${s.length>0?l`
|
|
914
1058
|
<div class="fetch-group-callers">
|
|
915
1059
|
<span class="fetch-group-callers-label">Called by</span>
|
|
916
|
-
${
|
|
917
|
-
</div>`:
|
|
1060
|
+
${s.map(a=>l`<span class="fetch-group-caller-pill">${a}</span>`)}
|
|
1061
|
+
</div>`:p}
|
|
918
1062
|
</div>
|
|
919
|
-
`}render(){let
|
|
1063
|
+
`}render(){let r=this.store.state.fetches,t=this.store.state.requests;if(r.length===0)return l`<bk-empty-state title="No fetches" subtitle="No outbound HTTP calls have been captured yet"></bk-empty-state>`;let e=this.buildGroups(r,t);return l`
|
|
920
1064
|
<div class="fetch-analysis" id="fetch-analysis">
|
|
921
|
-
${this.renderSummary(
|
|
922
|
-
${
|
|
923
|
-
<div class="fetch-groups-title">Grouped by URL (${
|
|
924
|
-
<div class="fetch-groups">${
|
|
925
|
-
`:
|
|
1065
|
+
${this.renderSummary(r)}
|
|
1066
|
+
${e.length>0?l`
|
|
1067
|
+
<div class="fetch-groups-title">Grouped by URL (${e.length})</div>
|
|
1068
|
+
<div class="fetch-groups">${e.map(s=>this.renderGroup(s))}</div>
|
|
1069
|
+
`:p}
|
|
926
1070
|
</div>
|
|
927
|
-
`}};u([
|
|
1071
|
+
`}};u([I({context:A})],te.prototype,"store",2),te=u([$("bk-fetches-view")],te);function tr(o,r){if(r>=400)return "var(--red)";switch(o){case "GET":return "var(--green)";case "POST":return "var(--blue)";case "PUT":case "PATCH":return "var(--amber)";case "DELETE":return "var(--red)";default:return "var(--text-muted)"}}function ts(o){return o==="query"?"var(--accent)":"var(--cyan)"}function li(o){return o.type==="query"||o.type==="fetch"}function ci(o){let r=(o.normalizedOp||o.operation||"QUERY").toUpperCase(),t=o.table||o.model||"";return {label:`${r} ${t}`,tooltip:o.sql||`${r} ${t}`}}function di(o){return {label:`${o.method} ${o.url}`,tooltip:`${o.method} ${o.url}`}}function pi(o,r,t,e,s,i){let n=o.data.durationMs||0,a,c;if(i){let d=Math.max(o.timestamp-e,0);a=Math.min(d/s*100,95),c=Math.max(n/s*100,1.5);}else {let d=t[0].timestamp,m=t[t.length-1].timestamp-d;a=m>0?(o.timestamp-d)/m*85:r/Math.max(t.length-1,1)*85,c=Math.max(n/s*100,1.5);}return a+c>100&&(c=Math.max(100-a,1.5)),{leftPct:a,widthPct:c}}function hi(o,r){let t=o.timeline.filter(li);if(t.length===0)return [];let e=r.startedAt,s=r.durationMs||1,i=Math.abs(t[0].timestamp-e)<s*10;return t.map((n,a)=>{let c=n.data.durationMs||0,{leftPct:d,widthPct:h}=pi(n,a,t,e,s,i),m=n.type==="query"?ci(n.data):di(n.data);return {type:n.type,label:m.label,durMs:c,durLabel:y(c),tooltip:m.tooltip,leftPct:d,widthPct:h}})}function sr(o,r){let t=o.requests.filter(a=>!a.isStrictModeDupe);if(t.length===0)return {rows:[],totalMs:0};let e=Math.min(...t.map(a=>a.startedAt)),i=Math.max(...t.map(a=>a.startedAt+a.durationMs))-e;return i===0?{rows:[],totalMs:0}:{rows:t.map(a=>{let c=(a.startedAt-e)/i*100,d=Math.max(a.durationMs/i*100,.5),h=r?.activities?.[a.id],m=h?hi(h,a):[];return {label:`${a.method} ${a.label}`,leftPct:c,widthPct:d,color:tr(a.method,a.statusCode),durMs:a.durationMs,durLabel:y(a.durationMs),tooltip:`${a.method} ${a.label} (${y(a.durationMs)})`,subEvents:m}}),totalMs:i}}function rr(o){let r=o.requests,t=[],e=[],s=[],i=[],n=new Map;for(let d of r){let h=d.label,m=d.pollingDurationMs||d.durationMs;if(!Yt[d.category||""]){if(d.isDuplicate){let f=n.get(h);f?(f.count++,f.wastedMs+=m):n.set(h,{name:h,count:2,wastedMs:m});continue}if(d.statusCode>=400){e.push(h+" ("+Vs(d.statusCode)+")");continue}d.responseSize>51200&&s.push("Large response: "+h+" returned "+ot(d.responseSize)),t.push(h);}}for(let d of n.values())i.push(d);let a="";if(i.length>0){let d=i.map(m=>m.name).join(", "),h=i.reduce((m,f)=>m+f.wastedMs,0);a="Your app fetches "+d+" multiple times on this page. This wastes ~"+y(h)+". Try caching these calls, deduplicating with React Query/SWR, or moving them to a shared layout.";}else e.length>0&&(a="Some requests are failing. Check your API routes and make sure the endpoints exist.");let c=r.filter(d=>d.durationMs>2e3&&d.category!==Et);return c.length>0&&!a&&(a=c.map(d=>d.label).join(", ")+` is taking over ${y(2e3)}. Consider adding caching or optimizing the backend query.`),{successes:t,errors:e,warnings:s,duplicates:i,tip:a}}var V=class extends b{constructor(){super(...arguments);this.expandedFlowIdx=-1;this.expandedSubReqIdx=-1;this.flowDetailTab="insights";this.flowTimeline=null;this.flowTimelineLoading=false;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}get flows(){return this.store.state.flows}get viewMode(){return this.store.state.viewMode}flowDotClass(t){return t.hasErrors?"dot-error":t.redundancyPct>0?"dot-warn":"dot-clean"}flowBadgeInfo(t){if(t.hasErrors){let e=t.requests.filter(s=>s.statusCode>=400).length;return {text:e+" error"+(e!==1?"s":""),cls:"badge-error"}}return t.redundancyPct>0?{text:t.redundancyPct+"% redundant",cls:"badge-warn"}:{text:"clean",cls:"badge-clean"}}toggleFlow(t){this.expandedFlowIdx===t?this.expandedFlowIdx=-1:(this.expandedFlowIdx=t,this.expandedSubReqIdx=-1,this.flowDetailTab="insights",this.flowTimeline=null);}toggleSubReq(t,e){e.stopPropagation(),this.expandedSubReqIdx=this.expandedSubReqIdx===t?-1:t;}toggleBodyBlock(t){t.stopPropagation();let e=t.currentTarget,s=e.parentElement;if(!s)return;e.classList.toggle("open");let i=s.querySelector("pre");i&&i.classList.toggle("open");}switchTab(t,e,s){s.stopPropagation(),this.flowDetailTab=t,t==="timeline"&&!this.flowTimeline&&this.loadFlowTimeline(e);}async loadFlowTimeline(t){if(this.flowTimelineLoading)return;let e=t.requests.map(s=>s.id).filter(Boolean);if(e.length!==0){this.flowTimelineLoading=true;try{let s=await fetch(`${T.activity}?requestIds=${e.join(",")}`);if(!s.ok){this.flowTimelineLoading=!1;return}this.flowTimeline=await s.json();}catch{}this.flowTimelineLoading=false;}}loadTimelineForContainer(t){let e=t.querySelectorAll(".request-timeline");for(let s of e){let i=s.getAttribute("data-request-id");if(i&&!s.hasAttribute("data-loaded")){s.setAttribute("data-loaded","1");let n=document.createElement("bk-timeline-panel");n.setAttribute("request-id",i),n.setAttribute("request-started",s.getAttribute("data-request-started")||"0"),s.appendChild(n),s.classList.remove("tl-hidden");}}}updated(){if(this.expandedFlowIdx>=0&&this.flowDetailTab==="insights"){let t=this.querySelector(".flow-expand.open");t&&this.loadTimelineForContainer(t);}}render(){let t=this.flows;return t.length===0?l`<bk-empty-state
|
|
928
1072
|
title="No actions yet"
|
|
929
1073
|
subtitle="Start using your app to see user action flows here"
|
|
930
|
-
></bk-empty-state>`:
|
|
1074
|
+
></bk-empty-state>`:l`
|
|
931
1075
|
<div id="flow-col-header" class="col-header">
|
|
932
1076
|
<span style="width:8px"></span>
|
|
933
1077
|
<span style="flex:1">Action</span>
|
|
@@ -936,15 +1080,15 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
936
1080
|
<span style="width:70px;text-align:right">Time</span>
|
|
937
1081
|
</div>
|
|
938
1082
|
<div id="flow-list">
|
|
939
|
-
${t.map((s
|
|
1083
|
+
${t.map((e,s)=>this.renderFlowRow(e,s))}
|
|
940
1084
|
</div>
|
|
941
|
-
`}renderFlowRow(t,
|
|
1085
|
+
`}renderFlowRow(t,e){let s=this.expandedFlowIdx===e,i=this.flowDotClass(t),n=this.flowBadgeInfo(t);return l`
|
|
942
1086
|
<div
|
|
943
|
-
class="flow-row ${
|
|
944
|
-
@click=${()=>this.toggleFlow(
|
|
1087
|
+
class="flow-row ${s?"expanded":""}"
|
|
1088
|
+
@click=${()=>this.toggleFlow(e)}
|
|
945
1089
|
>
|
|
946
1090
|
<div class="flow-summary-row">
|
|
947
|
-
<span class="flow-status-dot ${
|
|
1091
|
+
<span class="flow-status-dot ${i}"></span>
|
|
948
1092
|
<span class="flow-label">${t.label}</span>
|
|
949
1093
|
<span class="flow-req-count"
|
|
950
1094
|
>${t.requests.length}
|
|
@@ -952,39 +1096,39 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
952
1096
|
>
|
|
953
1097
|
<span class="flow-badge-pill ${n.cls}">${n.text}</span>
|
|
954
1098
|
<span class="flow-duration"
|
|
955
|
-
>${
|
|
1099
|
+
>${y(t.totalDurationMs)}</span
|
|
956
1100
|
>
|
|
957
1101
|
</div>
|
|
958
1102
|
</div>
|
|
959
|
-
<div class="flow-expand ${
|
|
960
|
-
${
|
|
1103
|
+
<div class="flow-expand ${s?"open":""}">
|
|
1104
|
+
${s?this.renderFlowDetail(t):p}
|
|
961
1105
|
</div>
|
|
962
|
-
`}renderFlowDetail(t){let
|
|
1106
|
+
`}renderFlowDetail(t){let e=this.viewMode==="simple"?"Insights":"Details";return l`
|
|
963
1107
|
<div class="flow-detail-tabs">
|
|
964
1108
|
<button
|
|
965
1109
|
class="flow-tab ${this.flowDetailTab==="insights"?"active":""}"
|
|
966
|
-
@click=${
|
|
1110
|
+
@click=${s=>this.switchTab("insights",t,s)}
|
|
967
1111
|
>
|
|
968
|
-
${
|
|
1112
|
+
${e}
|
|
969
1113
|
</button>
|
|
970
1114
|
<button
|
|
971
1115
|
class="flow-tab ${this.flowDetailTab==="timeline"?"active":""}"
|
|
972
|
-
@click=${
|
|
1116
|
+
@click=${s=>this.switchTab("timeline",t,s)}
|
|
973
1117
|
>
|
|
974
1118
|
Timeline
|
|
975
1119
|
</button>
|
|
976
1120
|
</div>
|
|
977
1121
|
${this.flowDetailTab==="insights"?this.viewMode==="simple"?this.renderFlowInsights(t):this.renderFlowSubReqs(t):this.renderFlowWaterfall(t)}
|
|
978
|
-
`}renderFlowWaterfall(t){if(this.flowTimelineLoading)return
|
|
1122
|
+
`}renderFlowWaterfall(t){if(this.flowTimelineLoading)return l`<div class="wf-loading">Loading timeline...</div>`;let{rows:e,totalMs:s}=sr(t,this.flowTimeline);if(e.length===0)return p;let i=[];for(let n=0;n<=5;n++)i.push(y(s/5*n));return l`
|
|
979
1123
|
<div class="flow-waterfall">
|
|
980
1124
|
<div class="wf-time-axis">
|
|
981
|
-
${
|
|
1125
|
+
${i.map(n=>l`<span>${n}</span>`)}
|
|
982
1126
|
</div>
|
|
983
1127
|
<div class="wf-rows">
|
|
984
|
-
${
|
|
1128
|
+
${e.map(n=>this.renderWaterfallGroup(n))}
|
|
985
1129
|
</div>
|
|
986
1130
|
</div>
|
|
987
|
-
`}renderWaterfallGroup(t){return
|
|
1131
|
+
`}renderWaterfallGroup(t){return l`
|
|
988
1132
|
<div class="wf-request-group">
|
|
989
1133
|
<div class="wf-req-row" title="${t.tooltip}">
|
|
990
1134
|
<div class="wf-req-label">${t.label}</div>
|
|
@@ -996,109 +1140,109 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
996
1140
|
</div>
|
|
997
1141
|
<div class="wf-req-dur">${t.durLabel}</div>
|
|
998
1142
|
</div>
|
|
999
|
-
${t.subEvents.length>0?t.subEvents.map(
|
|
1000
|
-
<div class="wf-sub-row" title="${
|
|
1143
|
+
${t.subEvents.length>0?t.subEvents.map(e=>l`
|
|
1144
|
+
<div class="wf-sub-row" title="${e.tooltip}">
|
|
1001
1145
|
<div class="wf-sub-label">
|
|
1002
1146
|
<span
|
|
1003
1147
|
class="wf-sub-dot"
|
|
1004
|
-
style="background:${
|
|
1148
|
+
style="background:${ts(e.type)}"
|
|
1005
1149
|
></span>
|
|
1006
|
-
${
|
|
1150
|
+
${e.label}
|
|
1007
1151
|
</div>
|
|
1008
1152
|
<div class="wf-bar-track">
|
|
1009
1153
|
<div
|
|
1010
1154
|
class="wf-bar wf-sub-bar-sized"
|
|
1011
|
-
style="left:${t.leftPct+
|
|
1155
|
+
style="left:${t.leftPct+e.leftPct/100*t.widthPct}%;width:${e.widthPct/100*t.widthPct}%;background:${ts(e.type)}"
|
|
1012
1156
|
></div>
|
|
1013
1157
|
</div>
|
|
1014
|
-
<div class="wf-sub-dur">${
|
|
1158
|
+
<div class="wf-sub-dur">${e.durLabel}</div>
|
|
1015
1159
|
</div>
|
|
1016
|
-
`):
|
|
1160
|
+
`):p}
|
|
1017
1161
|
</div>
|
|
1018
|
-
`}renderFlowInsights(t){let
|
|
1162
|
+
`}renderFlowInsights(t){let e=rr(t),s=e.errors.length>0||e.duplicates.length>0||e.warnings.length>0||!!e.tip;return l`
|
|
1019
1163
|
<div>
|
|
1020
1164
|
<div class="flow-traffic">
|
|
1021
|
-
${t.requests.map(
|
|
1165
|
+
${t.requests.map(i=>this.renderTrafficCard(i))}
|
|
1022
1166
|
</div>
|
|
1023
|
-
${
|
|
1167
|
+
${s?l`
|
|
1024
1168
|
<div class="flow-divider"></div>
|
|
1025
1169
|
<div class="flow-insights">
|
|
1026
|
-
${
|
|
1027
|
-
✗ ${
|
|
1170
|
+
${e.errors.map(i=>l`<div class="insight-line insight-error">
|
|
1171
|
+
✗ ${i}
|
|
1028
1172
|
</div>`)}
|
|
1029
|
-
${
|
|
1030
|
-
⚠ ${
|
|
1031
|
-
~${
|
|
1173
|
+
${e.duplicates.map(i=>l`<div class="insight-line insight-warn">
|
|
1174
|
+
⚠ ${i.name} — loaded ${i.count}x (wasting
|
|
1175
|
+
~${y(i.wastedMs)})
|
|
1032
1176
|
</div>`)}
|
|
1033
|
-
${
|
|
1034
|
-
${
|
|
1035
|
-
Tip: ${
|
|
1036
|
-
</div>`:
|
|
1177
|
+
${e.warnings.map(i=>l`<div class="insight-line insight-warn">⚠ ${i}</div>`)}
|
|
1178
|
+
${e.tip?l`<div class="insight-line insight-tip">
|
|
1179
|
+
Tip: ${e.tip}
|
|
1180
|
+
</div>`:p}
|
|
1037
1181
|
</div>
|
|
1038
|
-
`:
|
|
1182
|
+
`:p}
|
|
1039
1183
|
</div>
|
|
1040
|
-
`}renderTrafficCard(t){if(
|
|
1184
|
+
`}renderTrafficCard(t){if(Yt[t.category||""])return p;let e=St(t.statusCode),s=y(t.pollingDurationMs||t.durationMs),i=!t.isDuplicate&&t.category!==pe&&t.category!==Et||t.requestBody&&t.method!=="GET"||!!t.responseBody;return l`
|
|
1041
1185
|
<div
|
|
1042
1186
|
class="traffic-card ${t.isStrictModeDupe?"strict-mode-dupe":""}"
|
|
1043
1187
|
>
|
|
1044
|
-
<div class="traffic-card-header ${
|
|
1188
|
+
<div class="traffic-card-header ${i?"has-details":""}">
|
|
1045
1189
|
<bk-method-badge .method=${t.method}></bk-method-badge>
|
|
1046
1190
|
<span class="traffic-card-path ${t.isDuplicate?"is-dup":""}"
|
|
1047
1191
|
>${t.label}</span
|
|
1048
1192
|
>
|
|
1049
|
-
<span class="status-pill ${
|
|
1050
|
-
<span class="traffic-card-dur">${
|
|
1051
|
-
${t.isDuplicate?
|
|
1052
|
-
>${
|
|
1193
|
+
<span class="status-pill ${e}">${t.statusCode}</span>
|
|
1194
|
+
<span class="traffic-card-dur">${s}</span>
|
|
1195
|
+
${t.isDuplicate?l`<span class="traffic-card-dup">duplicate</span>`:l`<span class="traffic-card-size"
|
|
1196
|
+
>${ot(t.responseSize)}</span
|
|
1053
1197
|
>`}
|
|
1054
1198
|
</div>
|
|
1055
|
-
${t.isStrictModeDupe?
|
|
1199
|
+
${t.isStrictModeDupe?l`<div class="strict-mode-banner">
|
|
1056
1200
|
React Strict Mode duplicate — does not happen in production
|
|
1057
|
-
</div>`:
|
|
1058
|
-
${!t.isDuplicate&&t.category!==
|
|
1201
|
+
</div>`:p}
|
|
1202
|
+
${!t.isDuplicate&&t.category!==pe&&t.category!==Et?l`<div
|
|
1059
1203
|
class="request-timeline tl-hidden"
|
|
1060
1204
|
data-request-id=${t.id}
|
|
1061
1205
|
data-request-started=${String(t.startedAt)}
|
|
1062
|
-
></div>`:
|
|
1063
|
-
${t.requestBody&&t.method!=="GET"?this.renderBodyToggle("out","Request Body",t.requestBody):
|
|
1064
|
-
${t.responseBody?this.renderBodyToggle("in","Response Body",t.responseBody):
|
|
1206
|
+
></div>`:p}
|
|
1207
|
+
${t.requestBody&&t.method!=="GET"?this.renderBodyToggle("out","Request Body",t.requestBody):p}
|
|
1208
|
+
${t.responseBody?this.renderBodyToggle("in","Response Body",t.responseBody):p}
|
|
1065
1209
|
</div>
|
|
1066
|
-
`}renderBodyToggle(t,s
|
|
1210
|
+
`}renderBodyToggle(t,e,s){let i=t==="out"?"\u2192":"\u2190";return l`
|
|
1067
1211
|
<div class="traffic-body">
|
|
1068
1212
|
<button class="traffic-body-toggle" @click=${this.toggleBodyBlock}>
|
|
1069
1213
|
<span class="chevron">▸</span
|
|
1070
|
-
><span class="arrow-${t}">${
|
|
1214
|
+
><span class="arrow-${t}">${i}</span> ${e}
|
|
1071
1215
|
</button>
|
|
1072
|
-
<pre
|
|
1216
|
+
<pre>${G(dt(s))}</pre>
|
|
1073
1217
|
</div>
|
|
1074
|
-
`}renderFlowSubReqs(t){return
|
|
1075
|
-
${t.requests.map((s
|
|
1076
|
-
</div>`}renderSubReqRow(t,
|
|
1218
|
+
`}renderFlowSubReqs(t){return l`<div class="flow-subreqs">
|
|
1219
|
+
${t.requests.map((e,s)=>this.renderSubReqRow(e,s))}
|
|
1220
|
+
</div>`}renderSubReqRow(t,e){let s=this.expandedSubReqIdx===e,i=St(t.statusCode),n=t.pollingDurationMs?y(t.pollingDurationMs):y(t.durationMs);return l`
|
|
1077
1221
|
<div
|
|
1078
|
-
class="flow-subreq ${
|
|
1079
|
-
@click=${
|
|
1222
|
+
class="flow-subreq ${s?"expanded":""}"
|
|
1223
|
+
@click=${a=>this.toggleSubReq(e,a)}
|
|
1080
1224
|
>
|
|
1081
1225
|
<bk-method-badge .method=${t.method}></bk-method-badge>
|
|
1082
1226
|
<span class="subreq-label ${t.isDuplicate?"is-dup":""}"
|
|
1083
1227
|
>${t.path||t.url}</span
|
|
1084
1228
|
>
|
|
1085
|
-
${t.isDuplicate?
|
|
1086
|
-
<span class="status-pill ${
|
|
1229
|
+
${t.isDuplicate?l`<span class="subreq-dup-tag">duplicate</span>`:p}
|
|
1230
|
+
<span class="status-pill ${i}">${t.statusCode}</span>
|
|
1087
1231
|
<span class="subreq-dur">${n}</span>
|
|
1088
1232
|
</div>
|
|
1089
|
-
<div class="flow-subreq-detail ${
|
|
1090
|
-
${
|
|
1233
|
+
<div class="flow-subreq-detail ${s?"open":""}">
|
|
1234
|
+
${s?this.renderSubReqDetail(t):p}
|
|
1091
1235
|
</div>
|
|
1092
|
-
`}renderSubReqDetail(t){let
|
|
1236
|
+
`}renderSubReqDetail(t){let e=St(t.statusCode);return l`
|
|
1093
1237
|
<div class="detail-meta">
|
|
1094
1238
|
<span
|
|
1095
|
-
><bk-method-badge .method=${t.method}></bk-method-badge> ${
|
|
1239
|
+
><bk-method-badge .method=${t.method}></bk-method-badge> ${it(t.url)}</span
|
|
1096
1240
|
>
|
|
1097
1241
|
<span
|
|
1098
|
-
><span class="status-pill ${
|
|
1242
|
+
><span class="status-pill ${e}">${t.statusCode}</span></span
|
|
1099
1243
|
>
|
|
1100
1244
|
<span>${t.durationMs}ms</span>
|
|
1101
|
-
${t.responseSize?
|
|
1245
|
+
${t.responseSize?l`<span>${ot(t.responseSize)}</span>`:p}
|
|
1102
1246
|
</div>
|
|
1103
1247
|
<div
|
|
1104
1248
|
class="request-timeline tl-hidden"
|
|
@@ -1108,30 +1252,30 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
1108
1252
|
<div class="detail-grid">
|
|
1109
1253
|
<div class="detail-section">
|
|
1110
1254
|
<h4>Request Headers</h4>
|
|
1111
|
-
<pre
|
|
1255
|
+
<pre>${G(Tt(t.headers))}</pre>
|
|
1112
1256
|
</div>
|
|
1113
1257
|
<div class="detail-section">
|
|
1114
1258
|
<h4>Response Headers</h4>
|
|
1115
|
-
<pre
|
|
1259
|
+
<pre>${G(Tt(t.responseHeaders))}</pre>
|
|
1116
1260
|
</div>
|
|
1117
1261
|
<div class="detail-section">
|
|
1118
1262
|
<h4>Request Body</h4>
|
|
1119
|
-
<pre
|
|
1263
|
+
<pre>${G(dt(t.requestBody))}</pre>
|
|
1120
1264
|
</div>
|
|
1121
1265
|
<div class="detail-section">
|
|
1122
1266
|
<h4>Response Body</h4>
|
|
1123
|
-
<pre
|
|
1267
|
+
<pre>${G(dt(t.responseBody))}</pre>
|
|
1124
1268
|
</div>
|
|
1125
1269
|
</div>
|
|
1126
1270
|
<div class="detail-actions">
|
|
1127
1271
|
<button
|
|
1128
1272
|
class="btn btn-curl"
|
|
1129
|
-
@click=${
|
|
1273
|
+
@click=${s=>{s.stopPropagation(),Lt(t);}}
|
|
1130
1274
|
>
|
|
1131
1275
|
Copy cURL
|
|
1132
1276
|
</button>
|
|
1133
1277
|
</div>
|
|
1134
|
-
`}};u([
|
|
1278
|
+
`}};u([I({context:A})],V.prototype,"store",2),u([E()],V.prototype,"expandedFlowIdx",2),u([E()],V.prototype,"expandedSubReqIdx",2),u([E()],V.prototype,"flowDetailTab",2),u([E()],V.prototype,"flowTimeline",2),u([E()],V.prototype,"flowTimelineLoading",2),V=u([$("bk-flows-view")],V);var ee=class extends b{createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate());}render(){let r=(this.store.state.issues||[]).slice(),t=r.filter(a=>a.state==="open"||a.state==="fixing"||a.state==="regressed"),e=r.filter(a=>a.state==="resolved");if(t.length===0&&e.length===0)return this.store.state.requests.length>0||this.store.state.logs.length>0||this.store.state.queries.length>0?l`
|
|
1135
1279
|
<div class="sec-clear">
|
|
1136
1280
|
<span class="sec-clear-icon">\u2713</span>
|
|
1137
1281
|
<div class="sec-clear-text">
|
|
@@ -1139,180 +1283,142 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
1139
1283
|
<div class="sec-clear-sub">No security or quality issues detected this session</div>
|
|
1140
1284
|
</div>
|
|
1141
1285
|
</div>
|
|
1142
|
-
`:
|
|
1286
|
+
`:l`<bk-empty-state title="Waiting for requests..." subtitle="Start using your app to see security findings here"></bk-empty-state>`;let s=0,i=0,n=0;for(let a of t){let c=a.issue.severity;c==="critical"?s++:c==="info"?n++:i++;}return l`
|
|
1143
1287
|
<div id="security-content">
|
|
1144
|
-
${this.renderSummary(t.length,
|
|
1145
|
-
${t.length===0&&
|
|
1288
|
+
${this.renderSummary(t.length,e.length,s,i,n)}
|
|
1289
|
+
${t.length===0&&e.length>0?l`
|
|
1146
1290
|
<div class="sec-clear">
|
|
1147
1291
|
<span class="sec-clear-icon">\u2713</span>
|
|
1148
1292
|
<div class="sec-clear-text">
|
|
1149
1293
|
<div class="sec-clear-title">All issues resolved</div>
|
|
1150
|
-
<div class="sec-clear-sub">${
|
|
1294
|
+
<div class="sec-clear-sub">${e.length} finding${e.length!==1?"s were":" was"} detected and fixed</div>
|
|
1151
1295
|
</div>
|
|
1152
1296
|
</div>
|
|
1153
|
-
`:
|
|
1154
|
-
${t.length>0?this.renderOpenGroups(t):
|
|
1155
|
-
${
|
|
1297
|
+
`:p}
|
|
1298
|
+
${t.length>0?this.renderOpenGroups(t):p}
|
|
1299
|
+
${e.length>0?this.renderResolved(e):p}
|
|
1156
1300
|
</div>
|
|
1157
|
-
`}renderSummary(
|
|
1301
|
+
`}renderSummary(r,t,e,s,i){return l`
|
|
1158
1302
|
<div class="sec-summary">
|
|
1159
1303
|
<div class="sec-summary-left">
|
|
1160
|
-
<span class="sec-summary-count">${
|
|
1161
|
-
<span class="sec-summary-label">open issue${
|
|
1162
|
-
${t>0?
|
|
1304
|
+
<span class="sec-summary-count">${r}</span>
|
|
1305
|
+
<span class="sec-summary-label">open issue${r!==1?"s":""}</span>
|
|
1306
|
+
${t>0?l`<span class="sec-resolved-badge">${t} resolved</span>`:p}
|
|
1163
1307
|
</div>
|
|
1164
1308
|
<div class="sec-summary-right">
|
|
1165
|
-
${
|
|
1166
|
-
${
|
|
1167
|
-
${
|
|
1309
|
+
${e>0?l`<span class="sec-badge critical">${e} critical</span>`:p}
|
|
1310
|
+
${s>0?l`<span class="sec-badge warning">${s} warning</span>`:p}
|
|
1311
|
+
${i>0?l`<span class="sec-badge info">${i} info</span>`:p}
|
|
1168
1312
|
</div>
|
|
1169
1313
|
</div>
|
|
1170
|
-
`}renderOpenGroups(
|
|
1314
|
+
`}renderOpenGroups(r){let t={},e=[];for(let s of r){let i=s.issue,n=i.rule||i.type;t[n]||(t[n]={rule:n,title:i.title,severity:i.severity,hint:i.hint,items:[]},e.push(n)),t[n].items.push(s);}return e.sort((s,i)=>{let n=Y[t[s].severity]?.sort??2,a=Y[t[i].severity]?.sort??2;return n!==a?n-a:t[i].items.length-t[s].items.length}),l`${e.map(s=>this.renderGroup(t[s]))}`}renderGroup(r){let t=Y[r.severity]||Y.info;return l`
|
|
1171
1315
|
<div class="sec-group">
|
|
1172
1316
|
<div class="sec-group-header">
|
|
1173
1317
|
<span class="sec-group-icon ${t.cls}">${t.icon}</span>
|
|
1174
|
-
<span class="sec-group-title">${
|
|
1175
|
-
<span class="sec-group-count">${
|
|
1318
|
+
<span class="sec-group-title">${r.title}</span>
|
|
1319
|
+
<span class="sec-group-count">${r.items.length}</span>
|
|
1176
1320
|
</div>
|
|
1177
|
-
${
|
|
1178
|
-
<div class="sec-items">${
|
|
1321
|
+
${r.hint?l`<div class="sec-hint">${r.hint}</div>`:p}
|
|
1322
|
+
<div class="sec-items">${r.items.map(e=>this.renderIssueItem(e))}</div>
|
|
1179
1323
|
</div>
|
|
1180
|
-
`}renderIssueItem(
|
|
1324
|
+
`}renderIssueItem(r){let t=r.issue;return l`
|
|
1181
1325
|
<div class="sec-item">
|
|
1182
1326
|
<div class="sec-item-desc">${t.desc}</div>
|
|
1183
|
-
${
|
|
1184
|
-
${
|
|
1185
|
-
${
|
|
1327
|
+
${r.occurrences>1?l`<span class="sec-item-count">${r.occurrences}x</span>`:p}
|
|
1328
|
+
${r.state==="fixing"&&r.aiStatus==="fixed"?l`<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>`:r.aiStatus==="wont_fix"?l`<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>`:r.state==="regressed"?l`<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>`:p}
|
|
1329
|
+
${r.aiNotes?l`<div class="sec-ai-notes">${r.aiNotes}</div>`:p}
|
|
1186
1330
|
</div>
|
|
1187
|
-
`}renderResolved(
|
|
1331
|
+
`}renderResolved(r){return l`
|
|
1188
1332
|
<div class="sec-resolved-title">
|
|
1189
1333
|
<span class="sec-resolved-check">\u2713</span> Resolved
|
|
1190
|
-
<span class="sec-resolved-count">${
|
|
1334
|
+
<span class="sec-resolved-count">${r.length}</span>
|
|
1191
1335
|
</div>
|
|
1192
1336
|
<div class="sec-group sec-group-resolved">
|
|
1193
1337
|
<div class="sec-items">
|
|
1194
|
-
${
|
|
1338
|
+
${r.map(t=>l`
|
|
1195
1339
|
<div class="sec-item sec-item-resolved">
|
|
1196
1340
|
<span class="sec-resolved-item-icon">\u2713</span>
|
|
1197
1341
|
<div class="sec-item-desc">${t.issue.title} \u2014 ${t.issue.endpoint||"global"}</div>
|
|
1198
|
-
${t.aiStatus==="fixed"?
|
|
1199
|
-
${t.aiNotes?
|
|
1342
|
+
${t.aiStatus==="fixed"?l`<span class="sec-ai-badge sec-ai-verified">Verified fix</span>`:p}
|
|
1343
|
+
${t.aiNotes?l`<div class="sec-ai-notes">${t.aiNotes}</div>`:p}
|
|
1200
1344
|
</div>
|
|
1201
1345
|
`)}
|
|
1202
1346
|
</div>
|
|
1203
1347
|
</div>
|
|
1204
|
-
`}};u([
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
</div>`:d}
|
|
1213
|
-
${m.length===0&&T.length>0?a`<div class="ov-clear">
|
|
1214
|
-
<span class="ov-clear-icon">\u2713</span>All issues resolved \u2014
|
|
1215
|
-
${T.length} finding${T.length!==1?"s were":" was"} detected and
|
|
1216
|
-
fixed
|
|
1217
|
-
</div>`:d}
|
|
1218
|
-
${m.length>0?this.renderOpenIssues(m):d}
|
|
1219
|
-
${E.length>0?this.renderVerifying(E):d}
|
|
1220
|
-
${T.length>0?this.renderResolvedIssues(T):d}
|
|
1221
|
-
</div>
|
|
1222
|
-
`}renderSummary(t,s,r,o,n,l){return a`
|
|
1223
|
-
<div class="ov-summary">
|
|
1224
|
-
<div class="ov-stat"><span class="ov-stat-value">${t}</span><span class="ov-stat-label">Requests</span></div>
|
|
1225
|
-
<div class="ov-stat"><span class="ov-stat-value">${s}</span><span class="ov-stat-label">Actions</span></div>
|
|
1226
|
-
<div class="ov-stat"><span class="ov-stat-value">${v(r)}</span><span class="ov-stat-label">Avg Response</span></div>
|
|
1227
|
-
<div class="ov-stat"><span class="ov-stat-value">${o}</span><span class="ov-stat-label">Queries</span></div>
|
|
1228
|
-
<div class="ov-stat"><span class="ov-stat-value" style="color:${n>0?"var(--red)":"var(--green)"}">${n}</span><span class="ov-stat-label">Errors</span></div>
|
|
1229
|
-
<div class="ov-stat"><span class="ov-stat-value">${l}</span><span class="ov-stat-label">Fetches</span></div>
|
|
1230
|
-
</div>
|
|
1231
|
-
`}renderOpenIssues(t){return a`
|
|
1232
|
-
<div class="ov-section-title">Issues Found <span class="ov-issue-count">${t.length}</span></div>
|
|
1233
|
-
<div class="ov-cards">${t.map((s,r)=>this.renderIssueCard(s,r))}</div>
|
|
1234
|
-
`}renderIssueCard(t,s){let r=t.issue,o=q[r.severity]||q.info,n=this.expandedCardIdx===s,l=t.aiStatus==="wont_fix"?a`<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>`:t.state==="regressed"?a`<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>`:d,c=t.cleanHitsSinceLastSeen>0?a`<div class="ov-card-resolving">Resolving\u2026 ${t.cleanHitsSinceLastSeen}/${5} clean requests</div>`:d;return a`
|
|
1235
|
-
<div class="ov-card ${n?"expanded":""}" @click=${()=>this.toggleCard(s)}>
|
|
1236
|
-
<span class="ov-card-icon ${o.cls}">${o.icon}</span>
|
|
1237
|
-
<div class="ov-card-body">
|
|
1238
|
-
<div class="ov-card-title">${r.title}${l}</div>
|
|
1239
|
-
<div class="ov-card-desc">${r.desc}</div>
|
|
1240
|
-
${r.detail?a`<div class="ov-card-detail">${r.detail}</div>`:d}
|
|
1241
|
-
${c}
|
|
1242
|
-
${n&&r.hint?a`<div class="ov-card-hint">${r.hint}</div>`:d}
|
|
1348
|
+
`}};u([I({context:A})],ee.prototype,"store",2),ee=u([$("bk-security-view")],ee);var J=class extends b{constructor(){super(...arguments);this.handleStateChanged=()=>this.requestUpdate();}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",this.handleStateChanged);}disconnectedCallback(){super.disconnectedCallback(),this.store.removeEventListener("state-changed",this.handleStateChanged);}};u([I({context:A})],J.prototype,"store",2);async function Q(o){let r=await fetch(o);if(!r.ok)throw new Error(`HTTP ${r.status}: ${o}`);return r.json()}function B(o,r,t){return o===1?r:t??`${r}s`}function ir(o){return o.state!=="stale"}function $e(o){return (o.state==="open"||o.state==="regressed")&&o.aiStatus!=="wont_fix"}var se=class extends J{constructor(){super(...arguments);this.graphSummary=null;}connectedCallback(){super.connectedCallback(),this.loadGraphSummary();}async loadGraphSummary(){try{let e=(await Q(`${T.graph}?level=endpoints`)).nodes||[];this.graphSummary={endpoints:e.filter(s=>s.type==="endpoint").length,tables:e.filter(s=>s.type==="table").length,externals:e.filter(s=>s.type==="external").length};}catch{}}navigateTo(t){this.store.setActiveView(t);}navigateToExplorerErrors(){this.store.setActiveView("explorer"),window.dispatchEvent(new CustomEvent("navigate-explorer",{detail:"errors"}));}render(){let t=this.store.state,e=t.requests.filter(i=>!i.isStatic&&!i.isHealthCheck&&(!i.path||i.path.indexOf(U)!==0));return e.length>0||t.queries.length>0?l`
|
|
1349
|
+
<div class="ov-container">
|
|
1350
|
+
<div class="ov-grid">
|
|
1351
|
+
${this.renderActionsCard(t.flows)}
|
|
1352
|
+
${this.renderInsightsCard(t.issues)}
|
|
1353
|
+
${this.renderPerformanceCard(e)}
|
|
1354
|
+
${this.renderErrorsCard(e)}
|
|
1355
|
+
${this.renderGraphCard()}
|
|
1243
1356
|
</div>
|
|
1244
|
-
${r.hint?a`<span class="ov-card-arrow">${n?"\u2193":"\u2192"}</span>`:d}
|
|
1245
|
-
</div>
|
|
1246
|
-
`}renderVerifying(t){return a`
|
|
1247
|
-
<div class="ov-section-title ov-resolved-title">
|
|
1248
|
-
<span style="color:var(--yellow,#f5a623)">\u29d7</span> Awaiting Verification
|
|
1249
|
-
<span class="ov-issue-count">${t.length}</span>
|
|
1250
|
-
</div>
|
|
1251
|
-
<div class="ov-cards">
|
|
1252
|
-
${t.map(s=>{let r=s.issue,o=s.cleanHitsSinceLastSeen>0?a`<div class="ov-card-resolving">Verifying\u2026 ${s.cleanHitsSinceLastSeen}/${5} clean requests</div>`:d;return a`
|
|
1253
|
-
<div class="ov-card ov-card-resolved">
|
|
1254
|
-
<span class="ov-card-icon resolved">\u29d7</span>
|
|
1255
|
-
<div class="ov-card-body">
|
|
1256
|
-
<div class="ov-card-title" style="color:var(--text-muted)">
|
|
1257
|
-
${r.title}
|
|
1258
|
-
<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>
|
|
1259
|
-
</div>
|
|
1260
|
-
<div class="ov-card-desc">${r.desc}</div>
|
|
1261
|
-
${o}
|
|
1262
|
-
</div>
|
|
1263
|
-
</div>
|
|
1264
|
-
`})}
|
|
1265
1357
|
</div>
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
<div class="ov-card
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1358
|
+
`:l`<bk-empty-state
|
|
1359
|
+
title="${O.EMPTY_TITLE}"
|
|
1360
|
+
subtitle="${O.EMPTY_SUBTITLE_OVERVIEW}"
|
|
1361
|
+
></bk-empty-state>`}renderActionsCard(t){let e=t.length;if(e===0)return this.renderEmptyCard("\u25B6","Actions",O.NO_ACTIONS,"actions");let s=Math.round(t.reduce((n,a)=>n+a.totalDurationMs,0)/e),i=t[0]?.label||"";return this.renderCard("\u25B6",`${e} ${B(e,"action")}`,`avg ${y(s)}${i?` \xB7 "${i}"`:""}`,"actions","var(--accent)")}renderInsightsCard(t){let e=(t||[]).filter($e);if(e.length===0)return this.renderCard("\u2713",O.ALL_CLEAR,O.ALL_CLEAR_DETAIL,"insights","var(--green)");let s=e.filter(a=>a.issue.severity==="critical").length,i=e.filter(a=>a.issue.severity==="warning").length,n=[];return s>0&&n.push(`${s} critical`),i>0&&n.push(`${i} warning`),this.renderCard("\u{1F4A1}",`${e.length} ${B(e.length,"issue")}`,n.join(" \xB7 ")||O.REVIEW_RECOMMENDED,"insights","var(--amber)")}renderPerformanceCard(t){if(t.length===0)return this.renderEmptyCard("\u26A1","Performance",O.NO_REQUEST_DATA,"performance");let e=new Map;for(let n of t){let a=`${n.method} ${n.path}`,c=e.get(a);c||(c=[],e.set(a,c)),c.push(n.durationMs);}let s=[];for(let[n,a]of e){let c=Math.round(a.reduce((d,h)=>d+h,0)/a.length);c>2e3&&s.push({key:n,avg:c});}if(s.sort((n,a)=>a.avg-n.avg),s.length===0)return this.renderCard("\u26A1",O.ALL_FAST,O.NO_SLOW_ENDPOINTS,"performance","var(--green)");let i=s[0];return this.renderCard("\u26A1",`${s.length} slow ${B(s.length,"endpoint")}`,`worst: ${i.key} \xB7 ${y(i.avg)}`,"performance","var(--amber)")}renderErrorsCard(t){let e=t.filter(n=>n.statusCode>=400);if(e.length===0)return this.renderCard("\u2713",O.ZERO_ERRORS,O.ALL_REQUESTS_OK,"explorer","var(--green)");let s=new Map;for(let n of e)s.set(n.statusCode,(s.get(n.statusCode)||0)+1);let i=[...s.entries()].sort((n,a)=>a[1]-n[1]).slice(0,3).map(([n,a])=>`${a}\xD7 ${n}`);return l`
|
|
1362
|
+
<div class="ov-card-nav" @click=${()=>this.navigateToExplorerErrors()}>
|
|
1363
|
+
<div class="ov-card-icon-lg" style="color:var(--red)">\u2715</div>
|
|
1364
|
+
<div class="ov-card-content">
|
|
1365
|
+
<div class="ov-card-headline">${e.length} ${B(e.length,"error")}</div>
|
|
1366
|
+
<div class="ov-card-context">${i.join(" \xB7 ")}</div>
|
|
1367
|
+
</div>
|
|
1368
|
+
<span class="ov-card-arrow">\u2192</span>
|
|
1369
|
+
</div>
|
|
1370
|
+
`}renderGraphCard(){let t=this.graphSummary;if(!t||t.endpoints===0&&t.tables===0)return this.renderEmptyCard("\u25CE","Graph",O.BUILD_GRAPH,"graph");let e=[];t.endpoints>0&&e.push(`${t.endpoints} ${B(t.endpoints,"endpoint")}`),t.tables>0&&e.push(`${t.tables} ${B(t.tables,"table")}`);let s=t.externals>0?`${t.externals} external ${B(t.externals,"service")}`:"";return this.renderCard("\u25CE",e.join(" \xB7 "),s,"graph","var(--accent)")}renderCard(t,e,s,i,n){return l`
|
|
1371
|
+
<div class="ov-card-nav" @click=${()=>this.navigateTo(i)}>
|
|
1372
|
+
<div class="ov-card-icon-lg" style="color:${n}">${t}</div>
|
|
1373
|
+
<div class="ov-card-content">
|
|
1374
|
+
<div class="ov-card-headline">${e}</div>
|
|
1375
|
+
${s?l`<div class="ov-card-context">${s}</div>`:p}
|
|
1376
|
+
</div>
|
|
1377
|
+
<span class="ov-card-arrow">\u2192</span>
|
|
1281
1378
|
</div>
|
|
1282
|
-
`}
|
|
1379
|
+
`}renderEmptyCard(t,e,s,i){return l`
|
|
1380
|
+
<div class="ov-card-nav ov-card-empty" @click=${()=>this.navigateTo(i)}>
|
|
1381
|
+
<div class="ov-card-icon-lg">${t}</div>
|
|
1382
|
+
<div class="ov-card-content">
|
|
1383
|
+
<div class="ov-card-headline">${e}</div>
|
|
1384
|
+
<div class="ov-card-context">${s}</div>
|
|
1385
|
+
</div>
|
|
1386
|
+
<span class="ov-card-arrow">\u2192</span>
|
|
1387
|
+
</div>
|
|
1388
|
+
`}};u([E()],se.prototype,"graphSummary",2),se=u([$("bk-overview-view")],se);function or(o){return o<1?"<1ms":o<1e3?Math.round(o)+"ms":(o/1e3).toFixed(1)+"s"}function ui(o){return o<yt?Xt.green:o<_t?Xt.amber:Xt.red}function mi(o){return o.statusCode>=400?Xt.red:ui(o.durationMs)}function nr(o){return [parseInt(o.slice(1,3),16),parseInt(o.slice(3,5),16),parseInt(o.slice(5,7),16)]}function fi(o){let r=o.getContext("2d");if(!r)return null;let t=window.devicePixelRatio||1,e=o.clientWidth,s=o.clientHeight;return o.width=e*t,o.height=s*t,r.scale(t,t),{ctx:r,w:e,h:s}}function vi(o,r,t,e,s){let[i,n,a]=nr(s);o.beginPath(),o.arc(r,t,e+2,0,Math.PI*2),o.fillStyle=`rgba(${i},${n},${a},0.25)`,o.fill(),o.beginPath(),o.arc(r,t,e,0,Math.PI*2),o.fillStyle=s,o.fill();}function gi(o,r,t,e,s,i){let[n,a,c]=nr(s);o.strokeStyle=`rgba(${n},${a},${c},0.3)`,o.lineWidth=i+2,o.beginPath(),o.moveTo(r-e,t-e),o.lineTo(r+e,t+e),o.moveTo(r+e,t-e),o.lineTo(r-e,t+e),o.stroke(),o.strokeStyle=s,o.lineWidth=i,o.beginPath(),o.moveTo(r-e,t-e),o.lineTo(r+e,t+e),o.moveTo(r+e,t-e),o.lineTo(r-e,t+e),o.stroke();}function ar(o,r){let t=[],e=fi(o);if(!e||r.length===0)return t;let{ctx:s,w:i,h:n}=e,a=Gs,c=i-a.left-a.right,d=n-a.top-a.bottom,h=0,m=r[0].timestamp,f=r[0].timestamp;for(let _ of r)_.durationMs>h&&(h=_.durationMs),_.timestamp<m&&(m=_.timestamp),_.timestamp>f&&(f=_.timestamp);h=Math.max(h,10),h=Math.ceil(h*1.15/10)*10;let S=f-m||1;s.strokeStyle=Us,s.lineWidth=1;let R=4;for(let _=0;_<=R;_++){let v=a.top+d-_/R*d;s.beginPath(),s.moveTo(a.left,v),s.lineTo(a.left+c,v),s.stroke(),s.fillStyle=Xe,s.font=Fs,s.textAlign="right",s.fillText(or(Math.round(_/R*h)),a.left-8,v+3);}for(let _ of [{ms:yt},{ms:_t}]){if(_.ms>=h)continue;let v=a.top+d-_.ms/h*d;s.beginPath(),s.setLineDash([4,4]),s.strokeStyle="rgba(113,113,122,0.3)",s.lineWidth=1,s.moveTo(a.left,v),s.lineTo(a.left+c,v),s.stroke(),s.setLineDash([]),s.fillStyle="rgba(113,113,122,0.5)",s.font=Ke,s.textAlign="left",s.fillText(or(_.ms),a.left+c+2,v+3);}for(let _=0;_<r.length;_++){let v=r[_],g=r.length===1?a.left+c/2:a.left+(v.timestamp-m)/S*c,x=a.top+d-v.durationMs/h*d,w=mi(v);t.push({x:g,y:x,idx:_,r:v}),v.statusCode>=400?gi(s,g,x,4,w,2):vi(s,g,x,4,w);}s.fillStyle=Xe,s.font=Ke,s.textAlign="center";let P=[m,m+S/2,f];for(let _=0;_<P.length;_++){let v=a.left+_/2*c,g=new Date(P[_]);s.fillText(g.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"}),v,a.top+d+14);}return t}var xi={max:1/0,label:"Pending",color:"var(--text-muted)",bg:"var(--bg-muted)",border:"var(--border)"};function lr(o,r,t){return t>=20?o:r}function ss(o,r){if(!r||r<=0)return xi;let t=o/r;return t<.7?xt[0]:t<1.2?xt[1]:t<2?xt[2]:t<3?xt[3]:xt[4]}var X=class extends b{constructor(){super(...arguments);this.selectedEndpoint=rt;this.graphData=[];this.loadError=false;this.queryBreakdown=[];this.queryBreakdownLoading=false;this.scatterDots=[];}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate()),this.loadMetrics();}async loadMetrics(){try{let e=await(await fetch(T.metricsLive)).json();this.graphData=e.endpoints||[],this.loadError=!1,(!this.selectedEndpoint||this.selectedEndpoint===rt)&&(this.selectedEndpoint=rt);}catch{this.loadError=true;}}healthGradeForEndpoint(t){let e=lr(t.summary.p95Ms,t.summary.medianMs,t.summary.totalRequests);return ss(e,t.baselineP95Ms)}healthGradeForDuration(t,e){return ss(t,e)}getCallers(t){let e=this.store.state.flows,s=new Map;for(let i of e)for(let n of i.requests)if(`${n.method} ${n.path}`===t||this.normalizeEndpoint(n)===t){let c=s.get(i.label);c?(c.count++,c.totalMs+=n.durationMs):s.set(i.label,{count:1,totalMs:n.durationMs});}return [...s.entries()].map(([i,n])=>({label:i,count:n.count,avgMs:Math.round(n.totalMs/n.count)})).sort((i,n)=>n.count-i.count)}normalizeEndpoint(t){let s=t.path.split("?")[0].split("/").map(i=>{if(!i)return i;let n=i.length>0;for(let a=0;a<i.length;a++){let c=i.charCodeAt(a);if(c<48||c>57){n=false;break}}return n||i.length===36&&i[8]==="-"&&i[13]==="-"&&i[18]==="-"&&i[23]==="-"?":id":i}).join("/");return `${t.method} ${s}`}async loadQueryBreakdown(t){if(this.queryBreakdownLoading)return;let s=this.store.state.requests.filter(i=>`${i.method} ${i.path}`===t||this.normalizeEndpoint(i)===t).slice(-20).map(i=>i.id).filter(Boolean);if(s.length===0){this.queryBreakdown=[];return}this.queryBreakdownLoading=true;try{let i=await fetch(`${T.activity}?requestIds=${s.join(",")}`);if(!i.ok){this.queryBreakdownLoading=!1;return}let n=await i.json(),a=new Map;for(let c of Object.values(n.activities))for(let d of c.timeline){if(d.type!=="query")continue;let h=d.data,m=(h.normalizedOp||h.operation||"QUERY").toUpperCase(),f=h.table||h.model||"",S=`${m} ${f}`.trim(),R=a.get(S);R?(R.totalMs+=h.durationMs,R.count++):a.set(S,{label:S,totalMs:h.durationMs,count:1});}this.queryBreakdown=[...a.values()].map(c=>({...c,avgMs:Math.round(c.totalMs/c.count)})).sort((c,d)=>d.totalMs-c.totalMs);}catch{}this.queryBreakdownLoading=false;}renderScatterChart(t,e){this.scatterDots=ar(t,e),t.style.cursor="pointer",t.onclick=s=>{let i=t.getBoundingClientRect(),n=s.clientX-i.left,a=s.clientY-i.top,c=null,d=1/0;for(let h of this.scatterDots){let m=Math.sqrt((h.x-n)**2+(h.y-a)**2);m<d&&(d=m,c=h);}c&&d<16&&this.highlightRow(c.idx);};}highlightRow(t){let e=this.querySelector(".perf-hist-row-hl");e&&e.classList.remove("perf-hist-row-hl");let s=this.querySelector(`[data-req-idx="${t}"]`);s&&(s.classList.add("perf-hist-row-hl"),s.scrollIntoView({behavior:"smooth",block:"center"}));}updated(){if(this.selectedEndpoint===rt)return;let t=this.querySelector("#perf-detail-canvas");if(t){let e=this.graphData.find(s=>s.endpoint===this.selectedEndpoint);e&&this.renderScatterChart(t,e.requests);}}render(){return !this.graphData||this.graphData.length===0?l`<bk-empty-state title="No performance data yet" subtitle="Hit some endpoints and data will appear here"></bk-empty-state>`:l`
|
|
1283
1389
|
<div id="graph-content">
|
|
1284
1390
|
${this.renderSelector()}
|
|
1285
|
-
${this.selectedEndpoint===
|
|
1391
|
+
${this.selectedEndpoint===rt?this.renderOverview():this.renderDetail()}
|
|
1286
1392
|
</div>
|
|
1287
|
-
`}renderSelector(){return
|
|
1393
|
+
`}renderSelector(){return l`
|
|
1288
1394
|
<div class="perf-selector">
|
|
1289
|
-
<button class="perf-selector-btn ${this.selectedEndpoint===
|
|
1290
|
-
@click=${()=>{this.selectedEndpoint=
|
|
1291
|
-
${this.graphData.map((t,
|
|
1395
|
+
<button class="perf-selector-btn ${this.selectedEndpoint===rt?"active":""}"
|
|
1396
|
+
@click=${()=>{this.selectedEndpoint=rt;}}>Overview</button>
|
|
1397
|
+
${this.graphData.map((t,e)=>l`
|
|
1292
1398
|
<button class="perf-selector-btn ${t.endpoint===this.selectedEndpoint?"active":""}"
|
|
1293
1399
|
@click=${()=>{this.selectedEndpoint=t.endpoint,this.queryBreakdown=[],this.loadQueryBreakdown(t.endpoint);}}>
|
|
1294
|
-
<span class="perf-dot" style="background:${
|
|
1400
|
+
<span class="perf-dot" style="background:${Ve[e%Ve.length]}"></span>${t.endpoint}
|
|
1295
1401
|
</button>
|
|
1296
1402
|
`)}
|
|
1297
1403
|
</div>
|
|
1298
|
-
`}renderOverview(){let t=this.graphData.filter(c=>c.requests.length>0);if(t.length===0)return
|
|
1404
|
+
`}renderOverview(){let t=this.graphData.filter(c=>c.requests.length>0);if(t.length===0)return p;let e=t.reduce((c,d)=>c+d.summary.totalRequests,0),s=e>0?Math.round(t.reduce((c,d)=>c+d.summary.p95Ms*d.summary.totalRequests,0)/e):0,i=t.reduce((c,d)=>c+Math.round(d.summary.errorRate*d.summary.totalRequests),0),n=e>0?i/e:0,a=t[0];return l`
|
|
1299
1405
|
<div class="perf-overview">
|
|
1300
1406
|
<div class="perf-summary-row">
|
|
1301
1407
|
<div class="perf-summary-card">
|
|
1302
1408
|
<span class="perf-summary-label">Total Requests</span>
|
|
1303
|
-
<span class="perf-summary-value">${
|
|
1409
|
+
<span class="perf-summary-value">${e}</span>
|
|
1304
1410
|
</div>
|
|
1305
1411
|
<div class="perf-summary-card">
|
|
1306
1412
|
<span class="perf-summary-label">Avg P95</span>
|
|
1307
|
-
<span class="perf-summary-value" style="color:${this.healthGradeForDuration(
|
|
1413
|
+
<span class="perf-summary-value" style="color:${this.healthGradeForDuration(s).color}">${y(s)}</span>
|
|
1308
1414
|
</div>
|
|
1309
1415
|
<div class="perf-summary-card">
|
|
1310
1416
|
<span class="perf-summary-label">Error Rate</span>
|
|
1311
|
-
<span class="perf-summary-value" style="color:${
|
|
1417
|
+
<span class="perf-summary-value" style="color:${i>0?"var(--red)":"var(--green)"}">${Math.round(n*100)}%</span>
|
|
1312
1418
|
</div>
|
|
1313
1419
|
<div class="perf-summary-card">
|
|
1314
1420
|
<span class="perf-summary-label">Slowest</span>
|
|
1315
|
-
<span class="perf-summary-value perf-summary-value-sm">${
|
|
1421
|
+
<span class="perf-summary-value perf-summary-value-sm">${a?.endpoint??"-"}</span>
|
|
1316
1422
|
</div>
|
|
1317
1423
|
</div>
|
|
1318
1424
|
|
|
@@ -1332,50 +1438,50 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
1332
1438
|
</tbody>
|
|
1333
1439
|
</table>
|
|
1334
1440
|
</div>
|
|
1335
|
-
`}renderHeatmapRow(t){let
|
|
1441
|
+
`}renderHeatmapRow(t){let e=t.summary,s=this.healthGradeForEndpoint(t),i=Math.round(e.errorRate*e.totalRequests),n=(e.avgQueryTimeMs||0)+(e.avgFetchTimeMs||0)+(e.avgAppTimeMs||0),a=0,c=0,d=100;return n>0&&(a=Math.round((e.avgQueryTimeMs||0)/n*100),c=Math.round((e.avgFetchTimeMs||0)/n*100),d=Math.max(0,100-a-c)),l`
|
|
1336
1442
|
<tr class="perf-table-row" @click=${()=>{this.selectedEndpoint=t.endpoint,this.queryBreakdown=[],this.loadQueryBreakdown(t.endpoint);}}>
|
|
1337
1443
|
<td class="perf-td-name">${t.endpoint}</td>
|
|
1338
|
-
<td class="perf-td-right">${
|
|
1444
|
+
<td class="perf-td-right">${e.totalRequests}</td>
|
|
1339
1445
|
<td class="perf-td-center">
|
|
1340
|
-
<span class="perf-hm-p95" style="color:${
|
|
1446
|
+
<span class="perf-hm-p95" style="color:${s.color};background:${s.bg};border-color:${s.border}">${y(e.p95Ms)}</span>
|
|
1341
1447
|
</td>
|
|
1342
|
-
<td class="perf-td-center" style="color:${
|
|
1343
|
-
<td class="perf-td-center" style="color:${
|
|
1448
|
+
<td class="perf-td-center" style="color:${i>0?"var(--red)":"var(--text-muted)"}">${i>0?i:"-"}</td>
|
|
1449
|
+
<td class="perf-td-center" style="color:${e.avgQueryCount>5?"var(--amber)":"var(--text-muted)"}">${e.avgQueryCount}</td>
|
|
1344
1450
|
<td>
|
|
1345
1451
|
<span class="perf-hm-split-bar">
|
|
1346
|
-
${
|
|
1347
|
-
${c>0?
|
|
1348
|
-
${
|
|
1452
|
+
${a>0?l`<span class="perf-breakdown-seg perf-breakdown-db" style="width:${a}%"></span>`:p}
|
|
1453
|
+
${c>0?l`<span class="perf-breakdown-seg perf-breakdown-fetch" style="width:${c}%"></span>`:p}
|
|
1454
|
+
${d>0?l`<span class="perf-breakdown-seg perf-breakdown-app" style="width:${d}%"></span>`:p}
|
|
1349
1455
|
</span>
|
|
1350
1456
|
</td>
|
|
1351
1457
|
</tr>
|
|
1352
|
-
`}renderDetail(){let t=this.graphData.find(n=>n.endpoint===this.selectedEndpoint);if(!t?.requests?.length)return
|
|
1353
|
-
${this.renderDetailHeader(t,
|
|
1354
|
-
${this.renderDetailMetrics(s,
|
|
1355
|
-
${this.renderDetailBreakdown(
|
|
1458
|
+
`}renderDetail(){let t=this.graphData.find(n=>n.endpoint===this.selectedEndpoint);if(!t?.requests?.length)return l`<bk-empty-state subtitle="No data for this endpoint"></bk-empty-state>`;let e=t.summary,s=this.healthGradeForEndpoint(t),i=Math.round(e.errorRate*e.totalRequests);return l`
|
|
1459
|
+
${this.renderDetailHeader(t,s)}
|
|
1460
|
+
${this.renderDetailMetrics(e,s,i)}
|
|
1461
|
+
${this.renderDetailBreakdown(e)}
|
|
1356
1462
|
${this.renderCallers(t.endpoint)}
|
|
1357
1463
|
${this.renderQueryBreakdown()}
|
|
1358
1464
|
${this.renderTrends(t)}
|
|
1359
1465
|
${this.renderDetailChart()}
|
|
1360
1466
|
${this.renderDetailHistory(t)}
|
|
1361
|
-
`}renderDetailHeader(t,
|
|
1467
|
+
`}renderDetailHeader(t,e){return l`
|
|
1362
1468
|
<div class="perf-detail-header">
|
|
1363
1469
|
<div class="perf-detail-title">
|
|
1364
|
-
<span class="perf-badge perf-badge-lg" style="color:${
|
|
1470
|
+
<span class="perf-badge perf-badge-lg" style="color:${e.color};background:${e.bg};border-color:${e.border}">${e.label}</span>
|
|
1365
1471
|
<span>${t.endpoint}</span>
|
|
1366
|
-
${t.baselineP95Ms?
|
|
1472
|
+
${t.baselineP95Ms?l`<span class="perf-baseline-hint">Baseline: ${y(t.baselineP95Ms)}</span>`:p}
|
|
1367
1473
|
</div>
|
|
1368
1474
|
</div>
|
|
1369
|
-
`}renderDetailMetrics(t,s
|
|
1475
|
+
`}renderDetailMetrics(t,e,s){return l`
|
|
1370
1476
|
<div class="perf-metric-row">
|
|
1371
1477
|
<div class="perf-metric-card">
|
|
1372
1478
|
<span class="perf-metric-label">P95</span>
|
|
1373
|
-
<span class="perf-metric-value" style="color:${
|
|
1479
|
+
<span class="perf-metric-value" style="color:${e.color}">${y(t.p95Ms)}</span>
|
|
1374
1480
|
</div>
|
|
1375
1481
|
<div class="perf-metric-card">
|
|
1376
1482
|
<span class="perf-metric-label">Errors</span>
|
|
1377
|
-
<span class="perf-metric-value" style="color:${
|
|
1378
|
-
${
|
|
1483
|
+
<span class="perf-metric-value" style="color:${s>0?"var(--red)":"var(--green)"}">
|
|
1484
|
+
${s>0?s+" ("+Math.round(t.errorRate*100)+"%)":"0"}
|
|
1379
1485
|
</span>
|
|
1380
1486
|
</div>
|
|
1381
1487
|
<div class="perf-metric-card">
|
|
@@ -1383,71 +1489,71 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
1383
1489
|
<span class="perf-metric-value" style="color:${t.avgQueryCount>5?"var(--amber)":"var(--text)"}">${t.avgQueryCount}</span>
|
|
1384
1490
|
</div>
|
|
1385
1491
|
</div>
|
|
1386
|
-
`}renderDetailBreakdown(t){let
|
|
1492
|
+
`}renderDetailBreakdown(t){let e=(t.avgQueryTimeMs||0)+(t.avgFetchTimeMs||0)+(t.avgAppTimeMs||0);if(e<=0)return p;let s=Math.round((t.avgQueryTimeMs||0)/e*100),i=Math.round((t.avgFetchTimeMs||0)/e*100),n=Math.max(0,100-s-i);return l`
|
|
1387
1493
|
<div class="perf-breakdown">
|
|
1388
1494
|
<div class="perf-section-title">Time Breakdown</div>
|
|
1389
1495
|
<div class="perf-breakdown-bar">
|
|
1390
|
-
${
|
|
1391
|
-
${
|
|
1392
|
-
${n>0?
|
|
1496
|
+
${s>0?l`<div class="perf-breakdown-seg perf-breakdown-db" style="width:${s}%"></div>`:p}
|
|
1497
|
+
${i>0?l`<div class="perf-breakdown-seg perf-breakdown-fetch" style="width:${i}%"></div>`:p}
|
|
1498
|
+
${n>0?l`<div class="perf-breakdown-seg perf-breakdown-app" style="width:${n}%"></div>`:p}
|
|
1393
1499
|
</div>
|
|
1394
1500
|
<div class="perf-breakdown-legend">
|
|
1395
|
-
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-db"></span>DB ${
|
|
1396
|
-
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-fetch"></span>Fetch ${
|
|
1397
|
-
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-app"></span>App ${
|
|
1501
|
+
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-db"></span>DB ${y(t.avgQueryTimeMs||0)} (${s}%)</span>
|
|
1502
|
+
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-fetch"></span>Fetch ${y(t.avgFetchTimeMs||0)} (${i}%)</span>
|
|
1503
|
+
<span class="perf-breakdown-item"><span class="perf-breakdown-dot perf-breakdown-app"></span>App ${y(t.avgAppTimeMs||0)} (${n}%)</span>
|
|
1398
1504
|
</div>
|
|
1399
1505
|
</div>
|
|
1400
|
-
`}renderCallers(t){let
|
|
1506
|
+
`}renderCallers(t){let e=this.getCallers(t);return e.length===0?p:l`
|
|
1401
1507
|
<div class="perf-callers">
|
|
1402
1508
|
<div class="perf-section-title">Called By</div>
|
|
1403
1509
|
<div class="perf-callers-list">
|
|
1404
|
-
${
|
|
1510
|
+
${e.map(s=>l`
|
|
1405
1511
|
<div class="perf-caller-row">
|
|
1406
|
-
<span class="perf-caller-name">${
|
|
1407
|
-
<span class="perf-caller-count">${
|
|
1408
|
-
<span class="perf-caller-avg">avg ${
|
|
1512
|
+
<span class="perf-caller-name">${s.label}</span>
|
|
1513
|
+
<span class="perf-caller-count">${s.count} call${s.count!==1?"s":""}</span>
|
|
1514
|
+
<span class="perf-caller-avg">avg ${y(s.avgMs)}</span>
|
|
1409
1515
|
</div>
|
|
1410
1516
|
`)}
|
|
1411
1517
|
</div>
|
|
1412
1518
|
</div>
|
|
1413
|
-
`}renderQueryBreakdown(){return this.queryBreakdownLoading?
|
|
1519
|
+
`}renderQueryBreakdown(){return this.queryBreakdownLoading?l`<div class="perf-queries"><div class="perf-section-title">DB Queries</div><div class="perf-queries-loading">Loading...</div></div>`:this.queryBreakdown.length===0?p:l`
|
|
1414
1520
|
<div class="perf-queries">
|
|
1415
1521
|
<div class="perf-section-title">DB Queries</div>
|
|
1416
1522
|
<div class="perf-queries-list">
|
|
1417
|
-
${this.queryBreakdown.map(t=>
|
|
1523
|
+
${this.queryBreakdown.map(t=>l`
|
|
1418
1524
|
<div class="perf-query-row">
|
|
1419
1525
|
<span class="perf-query-label">${t.label}</span>
|
|
1420
|
-
<span class="perf-query-avg">avg ${
|
|
1526
|
+
<span class="perf-query-avg">avg ${y(t.avgMs)}</span>
|
|
1421
1527
|
<span class="perf-query-count">${t.count} call${t.count!==1?"s":""}</span>
|
|
1422
1528
|
</div>
|
|
1423
1529
|
`)}
|
|
1424
1530
|
</div>
|
|
1425
1531
|
</div>
|
|
1426
|
-
`}renderTrends(t){let
|
|
1532
|
+
`}renderTrends(t){let e=t.sessions;if(!e||e.length===0)return p;let s=e.slice(-10);return l`
|
|
1427
1533
|
<div class="perf-trends">
|
|
1428
1534
|
<div class="perf-section-title">Session Trend</div>
|
|
1429
1535
|
<div class="perf-trends-list">
|
|
1430
|
-
${
|
|
1536
|
+
${s.map((i,n)=>{let a=n>0?s[n-1].p95DurationMs:null,c=a!==null?i.p95DurationMs>a*1.2?"slower":i.p95DurationMs<a*.8?"faster":"":"",d=this.formatTimeAgo(i.startedAt),h=n===s.length-1,m=this.healthGradeForDuration(i.p95DurationMs,t.baselineP95Ms);return l`
|
|
1431
1537
|
<div class="perf-trend-row ${h?"perf-trend-current":""}">
|
|
1432
|
-
<span class="perf-trend-time">${h?"Current":
|
|
1538
|
+
<span class="perf-trend-time">${h?"Current":d}</span>
|
|
1433
1539
|
<span class="perf-trend-p95">
|
|
1434
1540
|
<span class="perf-hm-p95" style="color:${m.color};background:${m.bg};border-color:${m.border}">
|
|
1435
|
-
p95: ${
|
|
1541
|
+
p95: ${y(i.p95DurationMs)}
|
|
1436
1542
|
</span>
|
|
1437
1543
|
</span>
|
|
1438
|
-
<span class="perf-trend-reqs">${
|
|
1439
|
-
<span class="perf-trend-errs" style="color:${
|
|
1440
|
-
${c?
|
|
1544
|
+
<span class="perf-trend-reqs">${i.requestCount} req${i.requestCount!==1?"s":""}</span>
|
|
1545
|
+
<span class="perf-trend-errs" style="color:${i.errorCount>0?"var(--red)":"var(--text-dim)"}">${i.errorCount} err${i.errorCount!==1?"s":""}</span>
|
|
1546
|
+
${c?l`<span class="perf-trend-arrow ${c==="slower"?"perf-trend-slower":"perf-trend-faster"}">${c==="slower"?"\u2191 slower":"\u2193 faster"}</span>`:p}
|
|
1441
1547
|
</div>
|
|
1442
1548
|
`})}
|
|
1443
1549
|
</div>
|
|
1444
1550
|
</div>
|
|
1445
|
-
`}formatTimeAgo(t){let
|
|
1551
|
+
`}formatTimeAgo(t){let e=Date.now()-t,s=Math.round(e/6e4);if(s<1)return "just now";if(s<60)return `${s}m ago`;let i=Math.round(s/60);return i<24?`${i}h ago`:`${Math.round(i/24)}d ago`}renderDetailChart(){return l`
|
|
1446
1552
|
<div class="perf-chart-wrap">
|
|
1447
1553
|
<div class="perf-section-title">Response Time</div>
|
|
1448
1554
|
<canvas id="perf-detail-canvas" class="perf-canvas" style="width:100%;height:240px"></canvas>
|
|
1449
1555
|
</div>
|
|
1450
|
-
`}renderDetailHistory(t){if(t.requests.length===0)return
|
|
1556
|
+
`}renderDetailHistory(t){if(t.requests.length===0)return p;let e=[];for(let s=t.requests.length-1;s>=0&&e.length<50;s--)e.push({point:t.requests[s],originalIndex:s});return l`
|
|
1451
1557
|
<div class="perf-history-wrap">
|
|
1452
1558
|
<table class="perf-table">
|
|
1453
1559
|
<thead>
|
|
@@ -1461,145 +1567,538 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
1461
1567
|
</tr>
|
|
1462
1568
|
</thead>
|
|
1463
1569
|
<tbody>
|
|
1464
|
-
${
|
|
1570
|
+
${e.map(s=>this.renderHistoryRow(s.point,s.originalIndex,t.baselineP95Ms))}
|
|
1465
1571
|
</tbody>
|
|
1466
1572
|
</table>
|
|
1467
1573
|
</div>
|
|
1468
|
-
`}renderHistoryRow(t,s
|
|
1469
|
-
<tr class="perf-table-row ${
|
|
1574
|
+
`}renderHistoryRow(t,e,s){let i=this.healthGradeForDuration(t.durationMs,s),n=new Date(t.timestamp).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"}),a=t.statusCode>=400,c=t.queryTimeMs||0,d=t.fetchTimeMs||0,h=Math.max(0,t.durationMs-c-d);return l`
|
|
1575
|
+
<tr class="perf-table-row ${a?"perf-row-err":""}" data-req-idx=${e}>
|
|
1470
1576
|
<td class="perf-td-muted">${n}</td>
|
|
1471
1577
|
<td>
|
|
1472
|
-
<span class="perf-badge perf-badge-sm" style="color:${
|
|
1578
|
+
<span class="perf-badge perf-badge-sm" style="color:${i.color};background:${i.bg};border-color:${i.border}">${i.label}</span>
|
|
1473
1579
|
</td>
|
|
1474
|
-
<td>${
|
|
1580
|
+
<td>${y(t.durationMs)}</td>
|
|
1475
1581
|
<td>
|
|
1476
|
-
${c>0?
|
|
1477
|
-
${
|
|
1478
|
-
<span class="perf-bd-tag perf-bd-tag-app">App ${
|
|
1582
|
+
${c>0?l`<span class="perf-bd-tag perf-bd-tag-db">DB ${y(c)}</span>`:p}
|
|
1583
|
+
${d>0?l`<span class="perf-bd-tag perf-bd-tag-fetch">Fetch ${y(d)}</span>`:p}
|
|
1584
|
+
<span class="perf-bd-tag perf-bd-tag-app">App ${y(h)}</span>
|
|
1479
1585
|
</td>
|
|
1480
|
-
<td class="perf-td-center" style="color:${
|
|
1586
|
+
<td class="perf-td-center" style="color:${a?"var(--red)":"var(--text-muted)"}">${t.statusCode}</td>
|
|
1481
1587
|
<td class="perf-td-right perf-td-muted">${t.queryCount}</td>
|
|
1482
1588
|
</tr>
|
|
1483
|
-
`}};u([R({context:x})],M.prototype,"store",2),u([_()],M.prototype,"selectedEndpoint",2),u([_()],M.prototype,"graphData",2),u([_()],M.prototype,"loadError",2),u([_()],M.prototype,"queryBreakdown",2),u([_()],M.prototype,"queryBreakdownLoading",2),M=u([g("bk-performance-view")],M);function _r(i){return i===0?"<1ms":v(i)}var w=class extends f{constructor(){super(...arguments);this.requestId="";this.requestStarted=0;this.data=null;this.loading=false;this.failed=false;this.expandedSqlIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate()),this.requestId&&this.loadTimeline();}async loadTimeline(){if(!this.requestId)return;let t=w.cache.get(this.requestId);if(t){this.data=t;return}this.loading=true;try{let s=await fetch(`${$.activity}?requestId=${this.requestId}`);if(!s.ok){this.failed=!0,this.loading=!1;return}let r=await s.json();if(w.cache.size>=pe){let o=w.cache.keys().next().value;o!==void 0&&w.cache.delete(o);}w.cache.set(this.requestId,r),this.data=r,this.loading=!1;}catch(s){console.debug("[brakit] timeline load failed:",s),this.failed=true,this.loading=false;}}toggleSql(t,s){s.stopPropagation(),this.expandedSqlIdx=this.expandedSqlIdx===t?-1:t;}copySql(t,s){s.stopPropagation(),navigator.clipboard.writeText(t).then(()=>C.show("SQL copied")).catch(()=>C.show("Copy failed"));}render(){if(this.loading)return a`<div class="tl-loading">Loading activity...</div>`;if(this.failed||!this.data||this.data.total===0)return d;let t=this.data,s=t.timeline[0]?.timestamp??0;return a`
|
|
1589
|
+
`}};u([I({context:A})],X.prototype,"store",2),u([E()],X.prototype,"selectedEndpoint",2),u([E()],X.prototype,"graphData",2),u([E()],X.prototype,"loadError",2),u([E()],X.prototype,"queryBreakdown",2),u([E()],X.prototype,"queryBreakdownLoading",2),X=u([$("bk-performance-view")],X);var rs={auth:{label:"Auth",color:"#059669",icon:"\u{1F6E1}",tooltip:"Highlight which endpoints require authentication and which are unprotected"},security:{label:"Security",color:"#dc2626",icon:"\u26A0",tooltip:"Show security findings like exposed secrets, token leaks, and PII exposure"},performance:{label:"Perf",color:"#2563eb",icon:"\u26A1",tooltip:"Color endpoints by P95 latency \u2014 green (fast) to red (slow)"},issues:{label:"Issues",color:"#d97706",icon:"\u25CF",tooltip:"Badge endpoints with open issues like N+1 queries or redundant calls"},heat:{label:"Heat",color:"#ef4444",icon:"\u{1F525}",tooltip:"Color nodes and edges by traffic volume \u2014 blue (low) to red (hot)"}},Nt={action:{fill:"#faf5ff",stroke:"#a855f7",icon:"\u25B6",columnHeader:"ACTIONS"},endpoint:{fill:"#f8fafc",stroke:"#6366f1",icon:"\u26A1",columnHeader:"ENDPOINTS"},table:{fill:"#f0fdf4",stroke:"#16a34a",icon:"\u229E",columnHeader:"TABLES"},external:{fill:"#fffbeb",stroke:"#d97706",icon:"\u25C6",columnHeader:"EXTERNAL"}},xe={triggers:"#a855f7",reads:"#6366f1",writes:"#ef4444",fetches:"#f59e0b",calls:"#22c55e"},Ai=100,Ii=300,Ci=800;function kt(o){return o<Ai?"#22c55e":o<Ii?"#3b82f6":o<Ci?"#eab308":"#ef4444"}var Li=.25,Mi=.5,Ni=.75;function is(o){return o<Li?"#3b82f6":o<Mi?"#22c55e":o<Ni?"#eab308":"#ef4444"}var cr="#4338ca",dr="#e0e7ff",pr="#818cf8",hr="#eef2ff",os="#7c3aed",ur="#6366f1",Se="#f97316",mr="#ecfdf5",ns="#059669",fr="#fef2f2",as="#dc2626",vr="#fffbeb",ls="#d97706",ut="#ef4444",gr=.2,Er=3,cs=1.2,re=40;var br=800,yr=500;function Hi(o){return Math.max(140,o.length*7.2+36)}function ds(o,r){return Math.round(36+o.stats.requestCount/r*28)}function qi(o){return o.length>0?Math.max(140,...o.map(r=>Hi(r.label))):0}function $r(o,r,t){let e=[];for(let s of r){let i=s.source===o?s.target:s.target===o?s.source:null;if(i){let n=t.get(i);n&&e.push(n.y+n.h/2);}}return e.length>0?e.reduce((s,i)=>s+i,0)/e.length:1/0}function Sr(o,r){let t=v=>o.filter(g=>g.type===v).sort((g,x)=>x.stats.requestCount-g.stats.requestCount),e=t("action"),s=t("endpoint"),i=t("table"),n=t("external"),a=Math.max(1,...o.map(v=>v.stats.requestCount)),c=new Map,d=[],h=[],m=50,f=[{type:"action",items:e},{type:"endpoint",items:s},{type:"table",items:i},{type:"external",items:n}];for(let v of f)if(v.items.length>0){let g=qi(v.items);h.push({type:v.type,items:v.items,width:g,x:m}),m+=g+160;}let S=h[0];if(S){let v=74;for(let g of S.items){let x=ds(g,a),w={id:g.id,x:S.x,y:v,w:S.width,h:x,label:g.label,type:g.type,stats:g.stats,annotations:g.annotations};d.push(w),c.set(g.id,w),v+=x+10;}}for(let v=1;v<h.length;v++){let g=h[v],x=[...g.items].sort((C,F)=>$r(C.id,r,c)-$r(F.id,r,c)),w=d.filter(C=>C.x===h[v-1].x),H=Math.min(...w.map(C=>C.y)),j=Math.max(...w.map(C=>C.y+C.h))-H,tt=x.reduce((C,F)=>C+ds(F,a)+10,-10),W=H+Math.max(0,(j-tt)/2);for(let C of x){let F=ds(C,a),hs={id:C.id,x:g.x,y:W,w:g.width,h:F,label:C.label,type:C.type,stats:C.stats,annotations:C.annotations};d.push(hs),c.set(C.id,hs),W+=F+10;}}let R=[];for(let v of r){let g=c.get(v.source),x=c.get(v.target);if(!g||!x)continue;let w=g.x<x.x,H=w?g.x+g.w:g.x,Z=g.y+g.h/2,j=w?x.x:x.x+x.w,tt=x.y+x.h/2,W=[];v.stats.frequency>1&&W.push(`${v.stats.frequency}\xD7`),W.push(v.type),v.stats.avgLatencyMs>0&&W.push(`${v.stats.avgLatencyMs}ms`),R.push({key:v.id,sx:H,sy:Z,tx:j,ty:tt,label:W.join(" \xB7 "),color:xe[v.type]||"#94a3b8",thickness:Math.min(.75+Math.log2(v.stats.frequency+1)*.35,2.5),dashed:v.type==="reads"||v.type==="writes",data:v});}let P=d.reduce((v,g)=>Math.max(v,g.x+g.w),0),_=d.reduce((v,g)=>Math.max(v,g.y+g.h),0);return {nodes:d,edges:R,width:P+100,height:Math.max(_+50,250)}}function Tr(o){let r=o.charCodeAt(0);return r>=48&&r<=57||r>=65&&r<=70||r>=97&&r<=102}function Ui(o){if(o.length!==36)return false;for(let r=0;r<o.length;r++)if(r===8||r===13||r===18||r===23){if(o[r]!=="-")return false}else if(!Tr(o[r]))return false;return true}function Fi(o){if(!o.length)return false;for(let r=0;r<o.length;r++){let t=o.charCodeAt(r);if(t<48||t>57)return false}return true}function Gi(o){if(o.length<12)return false;for(let r=0;r<o.length;r++)if(!Tr(o[r]))return false;return true}function Bi(o){if(o.length<8)return false;let r=false,t=false;for(let e=0;e<o.length;e++){let s=o.charCodeAt(e);if(s>=65&&s<=90||s>=97&&s<=122)r=true;else if(s>=48&&s<=57)t=true;else if(s!==95&&s!==45)return false}return r&&t}var Wi=":id";function Qi(o){return Ui(o)||Fi(o)||Gi(o)||Bi(o)?Wi:o}function ps(o,r){let t=r.split("?")[0];return `${o} ${t.split("/").map(e=>e&&Qi(e)).join("/")}`}function wr(o,r,t){let e=[...o],s=[...r],i=new Set(e.map(a=>a.id)),n=new Map;for(let a of t){let c=a.label||"Unknown",d=n.get(c);d||(d={endpointKeys:new Set,count:0,totalMs:0},n.set(c,d)),d.count++,d.totalMs+=a.totalDurationMs;for(let h of a.requests)h.path?.startsWith(U)||d.endpointKeys.add(ps(h.method,h.path));}for(let[a,c]of n){let d=`action:${a}`;i.has(d)||(e.push({id:d,type:"action",label:a,stats:{requestCount:c.count,avgLatencyMs:c.count>0?Math.round(c.totalMs/c.count):0,errorRate:0,avgQueryCount:0}}),i.add(d));for(let h of c.endpointKeys){let m=`endpoint:${h}`;if(i.has(m)){let f=`${d} -> ${m}`;s.find(S=>S.id===f)||s.push({id:f,source:d,target:m,type:"triggers",stats:{frequency:c.count,avgLatencyMs:0}});}}}return {nodes:e,edges:s}}function Rr(o){let r=new Map;for(let t of o){let e=t.label||"Unknown",s=r.get(e);s||(s={keys:new Set,count:0,totalMs:0},r.set(e,s)),s.count++,s.totalMs+=t.totalDurationMs;for(let i of t.requests)i.path?.startsWith(U)||s.keys.add(ps(i.method,i.path));}return [...r.entries()].map(([t,e])=>({label:t,occurrences:e.count,endpointKeys:e.keys,avgDurationMs:e.count>0?Math.round(e.totalMs/e.count):0}))}function we(o,r){let t=`event=${encodeURIComponent(o)}${r?`&detail=${encodeURIComponent(r)}`:""}`;fetch(`${T.tab}?${t}`).catch(()=>{});}var M=class extends b{constructor(){super(...arguments);this.graphNodes=[];this.graphEdges=[];this.locked=null;this.hovered=null;this.loading=true;this.activeLayers=new Set;this.searchQuery="";this.viewTransform={x:0,y:0,scale:1};this.isPanning=false;this.panStart={x:0,y:0,vtx:0,vty:0};this.dragging=null;this.wasDragging=false;this.nodePositionOverrides=new Map;this.detailTab="overview";this.consolidatedFlows=[];this.activeFlowIdx=-1;this.focusIdx=-1;this.refreshTimer=null;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.loadData(),this.refreshTimer=setInterval(()=>this.loadData(),4e3);}disconnectedCallback(){super.disconnectedCallback(),this.refreshTimer&&clearInterval(this.refreshTimer);}async loadData(){try{let[t,e]=await Promise.all([fetch(`${T.graph}?level=endpoints`),fetch(T.flows)]),s=await t.json(),i=await e.json(),n=wr(s.nodes||[],s.edges||[],i.flows||[]);this.graphNodes=n.nodes,this.graphEdges=n.edges,this.consolidatedFlows=Rr(i.flows||[]),this.nodePositionOverrides.size===0&&this.graphNodes.length>0&&this.tryLoadPersistedPositions(),this.loading=!1;}catch{this.loading=false;}}getPositionStorageKey(){let t=this.graphNodes.map(s=>s.id).sort().join(","),e=0;for(let s=0;s<t.length;s++)e=(e<<5)-e+t.charCodeAt(s)|0;return `bk-graph-pos-${e}`}persistPositions(){if(this.nodePositionOverrides.size!==0)try{let t=Object.fromEntries(this.nodePositionOverrides);localStorage.setItem(this.getPositionStorageKey(),JSON.stringify(t));}catch{}}tryLoadPersistedPositions(){try{let t=localStorage.getItem(this.getPositionStorageKey());if(t){let e=JSON.parse(t);this.nodePositionOverrides=new Map(Object.entries(e));}}catch{}}get activeNodeId(){return this.locked??this.hovered}getHighlightedNodeIds(){let t=this.activeNodeId;if(!t)return null;let e=new Set([t]),s=true;for(;s;){s=false;for(let i of this.graphEdges)e.has(i.source)&&!e.has(i.target)&&(e.add(i.target),s=true);}return e}getFlowTraceNodeIds(){if(this.activeFlowIdx<0||this.activeFlowIdx>=this.consolidatedFlows.length)return null;let t=this.consolidatedFlows[this.activeFlowIdx],e=new Set;e.add(`action:${t.label}`);for(let s of t.endpointKeys)e.add(`endpoint:${s}`);for(let s of this.graphEdges)e.has(s.source)&&e.add(s.target);return e}getFlowTraceEdgeIds(){let t=this.getFlowTraceNodeIds();if(!t)return null;let e=new Set;for(let s of this.graphEdges)t.has(s.source)&&t.has(s.target)&&e.add(s.id);return e}matchesSearch(t){return this.searchQuery?t.toLowerCase().includes(this.searchQuery.toLowerCase()):true}handlePanStart(t){t.button===0&&(t.target.closest(".graph-g")||(this.isPanning=true,this.panStart={x:t.clientX,y:t.clientY,vtx:this.viewTransform.x,vty:this.viewTransform.y}));}handlePanMove(t){if(this.dragging){let e=t.currentTarget.getBoundingClientRect(),s=(t.clientX-e.left-this.viewTransform.x)/this.viewTransform.scale,i=(t.clientY-e.top-this.viewTransform.y)/this.viewTransform.scale;this.nodePositionOverrides.set(this.dragging.nodeId,{x:s-this.dragging.offsetX,y:i-this.dragging.offsetY}),this.requestUpdate();return}this.isPanning&&(this.viewTransform={...this.viewTransform,x:this.panStart.vtx+(t.clientX-this.panStart.x),y:this.panStart.vty+(t.clientY-this.panStart.y)});}handlePanEnd(){this.dragging&&(this.persistPositions(),this.dragging=null,this.wasDragging=true,requestAnimationFrame(()=>{this.wasDragging=false;})),this.isPanning=false;}resetView(){this.viewTransform={x:0,y:0,scale:1};}zoomIn(){this.viewTransform={...this.viewTransform,scale:Math.min(Er,this.viewTransform.scale*cs)};}zoomOut(){this.viewTransform={...this.viewTransform,scale:Math.max(gr,this.viewTransform.scale/cs)};}resetLayout(){this.nodePositionOverrides.clear();try{localStorage.removeItem(this.getPositionStorageKey());}catch{}this.viewTransform={x:0,y:0,scale:1},this.requestUpdate(),we("layout_reset");}startNodeDrag(t,e,s){t.stopPropagation();let i=t.currentTarget.closest("svg").getBoundingClientRect(),n=(t.clientX-i.left-this.viewTransform.x)/this.viewTransform.scale,a=(t.clientY-i.top-this.viewTransform.y)/this.viewTransform.scale;this.dragging={nodeId:e,offsetX:n-s.x,offsetY:a-s.y};}handleKeyDown(t){let e=this.graphNodes;if(t.key==="/"){t.preventDefault(),this.querySelector(".graph-search-input")?.focus();return}if(t.key==="Escape"){this.locked=null,this.searchQuery="",this.focusIdx=-1,this.activeFlowIdx=-1;return}if(t.key==="Tab"){t.preventDefault(),this.focusIdx=t.shiftKey?this.focusIdx<=0?e.length-1:this.focusIdx-1:(this.focusIdx+1)%e.length,this.hovered=e[this.focusIdx]?.id??null;return}if(t.key==="Enter"&&this.focusIdx>=0){let s=e[this.focusIdx];s&&(this.locked=this.locked===s.id?null:s.id);return}if(t.key==="+"||t.key==="="){this.zoomIn();return}if(t.key==="-"){this.zoomOut();return}if(t.key==="ArrowUp"){this.viewTransform={...this.viewTransform,y:this.viewTransform.y+re};return}if(t.key==="ArrowDown"){this.viewTransform={...this.viewTransform,y:this.viewTransform.y-re};return}if(t.key==="ArrowLeft"){this.viewTransform={...this.viewTransform,x:this.viewTransform.x+re};return}if(t.key==="ArrowRight"){this.viewTransform={...this.viewTransform,x:this.viewTransform.x-re};return}}render(){if(this.loading&&this.graphNodes.length===0)return l`<div class="graph-loading">Loading graph…</div>`;if(this.graphNodes.length===0)return l`
|
|
1590
|
+
<div class="graph-empty">
|
|
1591
|
+
<div class="graph-empty-icon">◎</div>
|
|
1592
|
+
<div class="graph-empty-title">No data yet</div>
|
|
1593
|
+
<div class="graph-empty-desc">Navigate your app to build the dependency graph.</div>
|
|
1594
|
+
</div>
|
|
1595
|
+
`;let t=Sr(this.graphNodes,this.graphEdges);this.applyPositionOverrides(t);let e=this.getSelectedNodeDetail(),s=this.getHighlightedNodeIds(),i=this.getFlowTraceNodeIds(),n=this.getFlowTraceEdgeIds(),a=this.deduplicateColumnHeaders(t.nodes),c=Math.max(1,...this.graphNodes.map(h=>h.stats.requestCount)),d=this.viewTransform;return l`
|
|
1596
|
+
<div class="graph-wrapper" tabindex="0" @keydown=${this.handleKeyDown}
|
|
1597
|
+
@click=${h=>{let m=h.target;m.closest(".graph-detail")||m.closest(".graph-toolbar")||m.closest(".graph-float")||m.closest(".graph-g")||(this.locked=null);}}>
|
|
1598
|
+
${this.renderToolbar()}
|
|
1599
|
+
<div class="graph-body">
|
|
1600
|
+
<div class="graph-canvas" style="position:relative">
|
|
1601
|
+
<svg width="100%" height="100%"
|
|
1602
|
+
viewBox="0 0 ${Math.max(t.width,br)} ${Math.max(t.height,yr)}"
|
|
1603
|
+
class="graph-svg"
|
|
1604
|
+
style="cursor:${this.isPanning?"grabbing":"grab"}"
|
|
1605
|
+
@mousedown=${this.handlePanStart}
|
|
1606
|
+
@mousemove=${this.handlePanMove}
|
|
1607
|
+
@mouseup=${this.handlePanEnd}
|
|
1608
|
+
@mouseleave=${this.handlePanEnd}>
|
|
1609
|
+
|
|
1610
|
+
<g transform="translate(${d.x},${d.y}) scale(${d.scale})">
|
|
1611
|
+
${k`${a.map(h=>k`
|
|
1612
|
+
<text x="${h.x}" y="${58}" class="graph-col-header">${h.label}</text>
|
|
1613
|
+
`)}`}
|
|
1614
|
+
|
|
1615
|
+
${t.edges.map(h=>this.renderEdge(h,s,n,c))}
|
|
1616
|
+
${t.nodes.map(h=>this.renderNode(h,s,i,c))}
|
|
1617
|
+
</g>
|
|
1618
|
+
</svg>
|
|
1619
|
+
${this.renderFloatingControls()}
|
|
1620
|
+
</div>
|
|
1621
|
+
${e?this.renderDetailPanel(e):p}
|
|
1622
|
+
</div>
|
|
1623
|
+
</div>
|
|
1624
|
+
`}applyPositionOverrides(t){for(let s of t.nodes){let i=this.nodePositionOverrides.get(s.id);i&&(s.x=i.x,s.y=i.y);}let e=new Map(t.nodes.map(s=>[s.id,s]));for(let s of t.edges){let i=e.get(s.data.source),n=e.get(s.data.target);if(i&&n){let a=i.x<n.x;s.sx=a?i.x+i.w:i.x,s.sy=i.y+i.h/2,s.tx=a?n.x:n.x+n.w,s.ty=n.y+n.h/2;}}}deduplicateColumnHeaders(t){let e=[],s=new Set;for(let i of t)s.has(i.type)||(s.add(i.type),e.push({x:i.x,label:Nt[i.type]?.columnHeader||i.type.toUpperCase()}));return e}renderToolbar(){return l`
|
|
1625
|
+
<div class="graph-toolbar">
|
|
1626
|
+
<div class="graph-layer-toggles">
|
|
1627
|
+
${Object.keys(rs).map(t=>{let e=rs[t],s=this.activeLayers.has(t);return l`
|
|
1628
|
+
<button class="graph-layer-btn ${s?"active":""}"
|
|
1629
|
+
style="${s?`border-color:${e.color};color:${e.color}`:""}"
|
|
1630
|
+
@click=${()=>this.toggleLayer(t)}
|
|
1631
|
+
title="${e.tooltip}">
|
|
1632
|
+
${e.icon} ${e.label}
|
|
1633
|
+
</button>
|
|
1634
|
+
`})}
|
|
1635
|
+
</div>
|
|
1636
|
+
|
|
1637
|
+
<div class="graph-search">
|
|
1638
|
+
<span class="graph-search-icon">⌕</span>
|
|
1639
|
+
<input class="graph-search-input" type="text" placeholder="Search nodes… ( / )"
|
|
1640
|
+
.value=${this.searchQuery}
|
|
1641
|
+
@input=${t=>{this.searchQuery=t.target.value;}}>
|
|
1642
|
+
${this.searchQuery?l`
|
|
1643
|
+
<button class="graph-search-clear" @click=${()=>{this.searchQuery="";}}>✕</button>
|
|
1644
|
+
`:p}
|
|
1645
|
+
</div>
|
|
1646
|
+
|
|
1647
|
+
${this.consolidatedFlows.length>0?l`
|
|
1648
|
+
<select class="graph-flow-picker" @change=${t=>{this.activeFlowIdx=parseInt(t.target.value,10),this.activeFlowIdx>=0&&we("flow_traced");}}>
|
|
1649
|
+
<option value="-1">Trace flow…</option>
|
|
1650
|
+
${this.consolidatedFlows.map((t,e)=>l`
|
|
1651
|
+
<option value="${e}" ?selected=${this.activeFlowIdx===e}>${t.label} → ${t.endpointKeys.size} ep · ${t.occurrences}×</option>
|
|
1652
|
+
`)}
|
|
1653
|
+
</select>
|
|
1654
|
+
`:p}
|
|
1655
|
+
|
|
1656
|
+
${this.activeLayers.has("auth")?l`
|
|
1657
|
+
<div class="graph-auth-legend">
|
|
1658
|
+
<span class="graph-auth-legend-item"><span style="color:${ns}">🛡</span> protected</span>
|
|
1659
|
+
<span class="graph-auth-legend-item"><span style="color:${Se};font-weight:700">!</span> no auth</span>
|
|
1660
|
+
</div>
|
|
1661
|
+
`:p}
|
|
1662
|
+
</div>
|
|
1663
|
+
`}renderFloatingControls(){let t=this.nodePositionOverrides.size>0,e=Math.round(this.viewTransform.scale*100);return l`
|
|
1664
|
+
<div class="graph-float">
|
|
1665
|
+
<button class="graph-float-btn" @click=${this.zoomOut} title="Zoom out (-)">−</button>
|
|
1666
|
+
<span class="graph-float-zoom">${e}%</span>
|
|
1667
|
+
<button class="graph-float-btn" @click=${this.zoomIn} title="Zoom in (+)">+</button>
|
|
1668
|
+
<span class="graph-float-sep"></span>
|
|
1669
|
+
<button class="graph-float-btn" @click=${this.resetView} title="Reset pan & zoom">⊙</button>
|
|
1670
|
+
${t?l`
|
|
1671
|
+
<span class="graph-float-sep"></span>
|
|
1672
|
+
<button class="graph-float-btn graph-float-btn-accent" @click=${()=>this.resetLayout()} title="Reset layout to auto-arrange">
|
|
1673
|
+
⟲ Reformat
|
|
1674
|
+
</button>
|
|
1675
|
+
`:p}
|
|
1676
|
+
<span class="graph-float-sep"></span>
|
|
1677
|
+
<button class="graph-float-btn" @click=${()=>this.captureScreenshot()} title="Save graph as PNG">
|
|
1678
|
+
📷
|
|
1679
|
+
</button>
|
|
1680
|
+
</div>
|
|
1681
|
+
`}async captureScreenshot(){we("screenshot_captured");let t=this.querySelector(".graph-svg");if(!t)return;let e=t.cloneNode(true);e.setAttribute("xmlns","http://www.w3.org/2000/svg"),e.style.cssText="",e.removeAttribute("class");let s=document.createElement("style");s.textContent=`
|
|
1682
|
+
text { font-family: 'Inter', system-ui, -apple-system, sans-serif; }
|
|
1683
|
+
.graph-col-header { fill: #c4c4cc; font-size: 9px; font-weight: 600; letter-spacing: 1.5px; }
|
|
1684
|
+
.graph-flow-edge { stroke-dasharray: 6,4; }
|
|
1685
|
+
.graph-pulse { }
|
|
1686
|
+
`,e.insertBefore(s,e.firstChild);let n=document.createElementNS("http://www.w3.org/2000/svg","rect");n.setAttribute("width","100%"),n.setAttribute("height","100%"),n.setAttribute("fill","#ffffff"),e.insertBefore(n,e.firstChild);let a=e.getAttribute("viewBox")||"0 0 800 500",[,,c,d]=a.split(" ").map(Number),h=2,m=c*h,f=d*h,S=new XMLSerializer().serializeToString(e),R=new Blob([S],{type:"image/svg+xml;charset=utf-8"}),P=URL.createObjectURL(R),_=new Image;_.onload=()=>{let v=document.createElement("canvas");v.width=m,v.height=f;let g=v.getContext("2d");g.fillStyle="#ffffff",g.fillRect(0,0,m,f),g.drawImage(_,0,0,m,f),URL.revokeObjectURL(P),v.toBlob(x=>{if(!x)return;let w=document.createElement("a");w.href=URL.createObjectURL(x),w.download=`brakit-graph-${new Date().toISOString().slice(0,19).replace(/[T:]/g,"-")}.png`,document.body.appendChild(w),w.click(),document.body.removeChild(w),URL.revokeObjectURL(w.href);},"image/png");},_.src=P;}toggleLayer(t){let e=new Set(this.activeLayers);e.has(t)?e.delete(t):(e.add(t),we("layer_toggled",t)),this.activeLayers=e;}renderNode(t,e,s,i){let n=this.activeNodeId,a=e===null||e.has(t.id),c=n===t.id,d=this.locked===t.id,h=s?.has(t.id)??false,m=this.matchesSearch(t.label),f=Nt[t.type]||Nt.endpoint,S=d?cr:c?pr:f.stroke,R=d?dr:c?hr:f.fill,P=d?2:c?1.5:.75,_=a?1:.08;this.searchQuery&&!m&&(_=.05),s&&!h&&(_=Math.min(_,.08)),h&&(_=1,S=os);let v=this.searchQuery&&m,g=t.annotations,x=this.activeLayers;x.has("performance")&&g?.p95Ms!==void 0&&t.type==="endpoint"&&(R=kt(g.p95Ms)+"18"),x.has("heat")&&(R=is(t.stats.requestCount/i)+"20");let w=x.has("auth")&&t.type==="endpoint"&&!g?.hasAuth,H=x.has("auth")&&g?.hasAuth,Z=x.has("security")?g?.securityFindings?.length??0:0,j=g?.securityFindings?.some(F=>F.severity==="critical"),tt=x.has("issues")?g?.openIssueCount??0:0,W=this.nodeSubtitle(t),C=x.has("performance")&&g?.p95Ms!==void 0&&t.type==="endpoint";return k`
|
|
1687
|
+
<g class="graph-g" transform="translate(${t.x},${t.y})" style="opacity:${_};cursor:pointer;transition:opacity .15s,transform .1s"
|
|
1688
|
+
@click=${F=>{F.stopPropagation(),!this.wasDragging&&(this.locked=this.locked===t.id?null:t.id,this.detailTab="overview");}}
|
|
1689
|
+
@mouseenter=${()=>{this.hovered=t.id;}}
|
|
1690
|
+
@mouseleave=${()=>{this.hovered=null;}}
|
|
1691
|
+
@mousedown=${F=>{F.detail>=2||this.startNodeDrag(F,t.id,t);}}>
|
|
1692
|
+
|
|
1693
|
+
${v?k`
|
|
1694
|
+
<rect x="-3" y="-3" width="${t.w+6}" height="${t.h+6}" rx="9" fill="none"
|
|
1695
|
+
stroke="${ur}" stroke-width="2" stroke-dasharray="4,2"/>
|
|
1696
|
+
`:p}
|
|
1697
|
+
|
|
1698
|
+
${w?k`
|
|
1699
|
+
<rect width="${t.w}" height="${t.h}" rx="8" fill="${R}" stroke="${Se}"
|
|
1700
|
+
stroke-width="1.2" stroke-dasharray="5,3"/>
|
|
1701
|
+
`:k`
|
|
1702
|
+
<rect width="${t.w}" height="${t.h}" rx="8" fill="${R}" stroke="${S}" stroke-width="${P}"/>
|
|
1703
|
+
`}
|
|
1704
|
+
|
|
1705
|
+
<text x="12" y="${t.h/2-4}" fill="#1e293b" font-size="11.5" font-weight="600"
|
|
1706
|
+
font-family="'Inter',system-ui,sans-serif">${f.icon} ${t.label}</text>
|
|
1707
|
+
<text x="12" y="${t.h/2+10}" fill="#a1a1aa" font-size="9"
|
|
1708
|
+
font-family="ui-monospace,monospace">${W}</text>
|
|
1709
|
+
|
|
1710
|
+
${C?k`
|
|
1711
|
+
<text x="${t.w-8}" y="${t.h-6}" fill="${kt(g.p95Ms)}" font-size="9"
|
|
1712
|
+
font-family="ui-monospace,monospace" text-anchor="end">p95: ${g.p95Ms}ms</text>
|
|
1713
|
+
`:p}
|
|
1714
|
+
|
|
1715
|
+
${H?k`
|
|
1716
|
+
<g transform="translate(${t.w-24},3)">
|
|
1717
|
+
<title>Auth protected</title>
|
|
1718
|
+
<rect width="18" height="18" rx="3" fill="${mr}" stroke="${ns}" stroke-width="0.5"/>
|
|
1719
|
+
<text x="9" y="13" text-anchor="middle" font-size="10">🛡</text>
|
|
1720
|
+
</g>
|
|
1721
|
+
`:p}
|
|
1722
|
+
|
|
1723
|
+
${w?k`
|
|
1724
|
+
<g transform="translate(${t.w-24},3)">
|
|
1725
|
+
<title>No auth detected — this endpoint may be unprotected</title>
|
|
1726
|
+
<rect width="18" height="18" rx="3" fill="#fff7ed" stroke="${Se}" stroke-width="0.5"/>
|
|
1727
|
+
<text x="9" y="13" text-anchor="middle" font-size="10" fill="#ea580c">!</text>
|
|
1728
|
+
</g>
|
|
1729
|
+
`:p}
|
|
1730
|
+
|
|
1731
|
+
${Z>0?k`
|
|
1732
|
+
<g transform="translate(${t.w-(H||w?46:24)},3)">
|
|
1733
|
+
<rect width="18" height="18" rx="3" fill="${j?fr:vr}"
|
|
1734
|
+
stroke="${j?as:ls}" stroke-width="0.5"
|
|
1735
|
+
class="${j?"graph-pulse":""}"/>
|
|
1736
|
+
<text x="9" y="13" text-anchor="middle" font-size="9" font-weight="600"
|
|
1737
|
+
fill="${j?as:ls}">${Z}</text>
|
|
1738
|
+
</g>
|
|
1739
|
+
`:p}
|
|
1740
|
+
|
|
1741
|
+
${tt>0?k`
|
|
1742
|
+
<circle cx="${t.w-8}" cy="8" r="5" fill="${ut}" stroke="white" stroke-width="1"/>
|
|
1743
|
+
<text x="${t.w-8}" y="11" text-anchor="middle" font-size="7" fill="white" font-weight="700">${tt}</text>
|
|
1744
|
+
`:t.stats.errorRate>.05?k`<circle cx="${t.w-12}" cy="12" r="4" fill="${ut}"/>`:p}
|
|
1745
|
+
|
|
1746
|
+
${g?.isMiddleware&&x.has("auth")?k`
|
|
1747
|
+
<text x="${t.w}" y="${t.h+12}" text-anchor="end" font-size="8" fill="#6b7280"
|
|
1748
|
+
font-family="ui-monospace,monospace">middleware</text>
|
|
1749
|
+
`:p}
|
|
1750
|
+
</g>
|
|
1751
|
+
`}nodeSubtitle(t){switch(t.type){case "endpoint":return `${t.stats.requestCount} req \xB7 ${t.stats.avgLatencyMs}ms${t.stats.avgQueryCount>0?` \xB7 ${t.stats.avgQueryCount}q`:""}`;case "action":return `${t.stats.requestCount}\xD7 \xB7 ${t.stats.avgLatencyMs}ms`;case "table":return "table";default:return "service"}}renderEdge(t,e,s,i){let n=this.activeNodeId,a=e===null||e.has(t.data.source)&&e.has(t.data.target),c=s?.has(t.key)??false,d=a?n===null?.25:.6:.04,h=a&&n!==null,m=t.color,f=t.thickness;if(s&&!c&&(d=.04),c&&(d=.85,m=os,f=Math.max(f,1.8)),this.activeLayers.has("heat")&&!c){let tt=Math.max(1,...this.graphEdges.map(C=>C.stats.frequency)),W=t.data.stats.frequency/tt;m=is(W),a&&(d=Math.max(d,.4));}let S=this.activeLayers.has("issues")&&t.data.annotations?.hasIssue,R=Math.abs(t.tx-t.sx),P=Math.min(R*.45,120),_=t.sx<t.tx,v=_?t.sx+P:t.sx-P,g=_?t.tx-P:t.tx+P,x=(t.sx+t.tx)/2,w=(t.sy+t.ty)/2,H=4,Z=S?ut:m,j=S?Math.max(f,1.5):f;return k`
|
|
1752
|
+
<g style="transition:opacity .15s">
|
|
1753
|
+
<path d="M${t.sx},${t.sy} C${v},${t.sy} ${g},${t.ty} ${t.tx},${t.ty}"
|
|
1754
|
+
fill="none" stroke="${Z}" stroke-width="${j}"
|
|
1755
|
+
stroke-opacity="${d}" stroke-linecap="round"
|
|
1756
|
+
stroke-dasharray="${c?"6,4":t.dashed?"3,3":"none"}"
|
|
1757
|
+
class="${c?"graph-flow-edge":""}"/>
|
|
1758
|
+
<polygon points="${t.tx},${t.ty} ${t.tx+(_?-H*1.5:H*1.5)},${t.ty-H} ${t.tx+(_?-H*1.5:H*1.5)},${t.ty+H}"
|
|
1759
|
+
fill="${Z}" fill-opacity="${d}"/>
|
|
1760
|
+
${h?k`
|
|
1761
|
+
<rect x="${x-t.label.length*2.8-2}" y="${w-7}" width="${t.label.length*5.6+8}" height="14"
|
|
1762
|
+
rx="4" fill="white" fill-opacity="0.92" stroke="${m}" stroke-width="0.4" stroke-opacity="0.15"/>
|
|
1763
|
+
<text x="${x}" y="${w+3.5}" fill="${m}" font-size="8" font-weight="500"
|
|
1764
|
+
font-family="ui-monospace,monospace" text-anchor="middle" opacity="0.85">${t.label}</text>
|
|
1765
|
+
`:p}
|
|
1766
|
+
${S?k`
|
|
1767
|
+
<text x="${x}" y="${w-10}" fill="${ut}" font-size="8" font-weight="600"
|
|
1768
|
+
text-anchor="middle">⚠ N+1</text>
|
|
1769
|
+
`:p}
|
|
1770
|
+
</g>
|
|
1771
|
+
`}renderDetailPanel(t){let{node:e,edges:s}=t,i=Nt[e.type]||Nt.endpoint,n=e.annotations,a=(n?.securityFindings?.length??0)>0,c=(n?.openIssueCount??0)>0,d=n?.p95Ms!==void 0,m=[{key:"overview",label:"Overview",show:true},{key:"security",label:`Security${a?` (${n.securityFindings.length})`:""}`,show:a},{key:"performance",label:"Perf",show:d},{key:"issues",label:`Issues${c?` (${n.openIssueCount})`:""}`,show:c}].filter(f=>f.show);return l`
|
|
1772
|
+
<div class="graph-detail">
|
|
1773
|
+
<div class="graph-detail-head">
|
|
1774
|
+
<div>
|
|
1775
|
+
<div class="graph-detail-badge" style="color:${i.stroke}">${i.icon} ${e.type}</div>
|
|
1776
|
+
<div class="graph-detail-name">${e.label}</div>
|
|
1777
|
+
${n?.hasAuth?l`<span class="graph-detail-auth-badge">🛡 Authenticated</span>`:p}
|
|
1778
|
+
${n?.isMiddleware?l`<span class="graph-detail-mw-badge">middleware</span>`:p}
|
|
1779
|
+
</div>
|
|
1780
|
+
<button class="graph-detail-close" @click=${()=>{this.locked=null;}}>✕</button>
|
|
1781
|
+
</div>
|
|
1782
|
+
|
|
1783
|
+
${m.length>1?l`
|
|
1784
|
+
<div class="graph-detail-tabs">
|
|
1785
|
+
${m.map(f=>l`
|
|
1786
|
+
<button class="graph-detail-tab ${this.detailTab===f.key?"active":""}"
|
|
1787
|
+
@click=${()=>{this.detailTab=f.key;}}>${f.label}</button>
|
|
1788
|
+
`)}
|
|
1789
|
+
</div>
|
|
1790
|
+
`:p}
|
|
1791
|
+
|
|
1792
|
+
${this.detailTab==="overview"?this.renderOverviewTab(e,s):p}
|
|
1793
|
+
${this.detailTab==="security"?this.renderSecurityTab(n):p}
|
|
1794
|
+
${this.detailTab==="performance"?this.renderPerformanceTab(e,n):p}
|
|
1795
|
+
${this.detailTab==="issues"?this.renderIssuesTab(n):p}
|
|
1796
|
+
</div>
|
|
1797
|
+
`}renderOverviewTab(t,e){return l`
|
|
1798
|
+
<div class="graph-detail-stats">
|
|
1799
|
+
<div class="graph-detail-stat">
|
|
1800
|
+
<div class="graph-detail-val">${t.stats.requestCount}</div>
|
|
1801
|
+
<div class="graph-detail-lbl">${t.type==="action"?"OCCURRENCES":"REQUESTS"}</div>
|
|
1802
|
+
</div>
|
|
1803
|
+
<div class="graph-detail-stat">
|
|
1804
|
+
<div class="graph-detail-val" style="color:${kt(t.stats.avgLatencyMs)}">${t.stats.avgLatencyMs}ms</div>
|
|
1805
|
+
<div class="graph-detail-lbl">AVG LATENCY</div>
|
|
1806
|
+
</div>
|
|
1807
|
+
${t.stats.avgQueryCount>0?l`
|
|
1808
|
+
<div class="graph-detail-stat">
|
|
1809
|
+
<div class="graph-detail-val">${t.stats.avgQueryCount}</div>
|
|
1810
|
+
<div class="graph-detail-lbl">QUERIES/REQ</div>
|
|
1811
|
+
</div>
|
|
1812
|
+
`:p}
|
|
1813
|
+
${t.stats.errorRate>.01?l`
|
|
1814
|
+
<div class="graph-detail-stat">
|
|
1815
|
+
<div class="graph-detail-val" style="color:${ut}">${Math.round(t.stats.errorRate*100)}%</div>
|
|
1816
|
+
<div class="graph-detail-lbl">ERRORS</div>
|
|
1817
|
+
</div>
|
|
1818
|
+
`:p}
|
|
1819
|
+
</div>
|
|
1820
|
+
|
|
1821
|
+
${e.length>0?l`
|
|
1822
|
+
<div class="graph-detail-sec">Connections</div>
|
|
1823
|
+
${e.map(s=>{let i=s.source===this.locked,n=(i?s.target:s.source).replace(/^(action|endpoint|table|external):/,"");return l`
|
|
1824
|
+
<div class="graph-detail-conn">
|
|
1825
|
+
<span class="graph-detail-edge-dot" style="background:${xe[s.type]}"></span>
|
|
1826
|
+
<span class="graph-detail-edge-type">${s.type}</span>
|
|
1827
|
+
<span>${i?"\u2192":"\u2190"} ${n}</span>
|
|
1828
|
+
<span class="graph-detail-dim">${s.stats.frequency}× · ${s.stats.avgLatencyMs}ms</span>
|
|
1829
|
+
</div>
|
|
1830
|
+
`})}
|
|
1831
|
+
`:p}
|
|
1832
|
+
|
|
1833
|
+
${e.some(s=>s.patterns?.length)?l`
|
|
1834
|
+
<div class="graph-detail-sec">SQL Patterns</div>
|
|
1835
|
+
${e.filter(s=>s.patterns).flatMap(s=>s.patterns).map(s=>l`
|
|
1836
|
+
<pre class="graph-detail-sql">${s.length>200?s.slice(0,200)+"\u2026":s}</pre>
|
|
1837
|
+
`)}
|
|
1838
|
+
`:p}
|
|
1839
|
+
`}renderSecurityTab(t){return t?.securityFindings?.length?l`
|
|
1840
|
+
${t.securityFindings.map(e=>l`
|
|
1841
|
+
<div class="graph-detail-finding">
|
|
1842
|
+
<span class="graph-detail-severity graph-detail-severity-${e.severity}">${e.severity}</span>
|
|
1843
|
+
<div class="graph-detail-finding-title">${e.title}</div>
|
|
1844
|
+
<div class="graph-detail-finding-meta">${e.rule} · ${e.count} occurrence${e.count!==1?"s":""}</div>
|
|
1845
|
+
</div>
|
|
1846
|
+
`)}
|
|
1847
|
+
`:l`<div class="graph-detail-empty">No security findings</div>`}renderPerformanceTab(t,e){return l`
|
|
1848
|
+
<div class="graph-detail-stats">
|
|
1849
|
+
${e?.p95Ms!==void 0?l`
|
|
1850
|
+
<div class="graph-detail-stat">
|
|
1851
|
+
<div class="graph-detail-val" style="color:${kt(e.p95Ms)}">${e.p95Ms}ms</div>
|
|
1852
|
+
<div class="graph-detail-lbl">P95 LATENCY</div>
|
|
1853
|
+
</div>
|
|
1854
|
+
`:p}
|
|
1855
|
+
<div class="graph-detail-stat">
|
|
1856
|
+
<div class="graph-detail-val" style="color:${kt(t.stats.avgLatencyMs)}">${t.stats.avgLatencyMs}ms</div>
|
|
1857
|
+
<div class="graph-detail-lbl">AVG LATENCY</div>
|
|
1858
|
+
</div>
|
|
1859
|
+
${t.stats.avgQueryCount>0?l`
|
|
1860
|
+
<div class="graph-detail-stat">
|
|
1861
|
+
<div class="graph-detail-val">${t.stats.avgQueryCount}</div>
|
|
1862
|
+
<div class="graph-detail-lbl">QUERIES/REQ</div>
|
|
1863
|
+
</div>
|
|
1864
|
+
`:p}
|
|
1865
|
+
<div class="graph-detail-stat">
|
|
1866
|
+
<div class="graph-detail-val">${t.stats.requestCount}</div>
|
|
1867
|
+
<div class="graph-detail-lbl">TOTAL REQS</div>
|
|
1868
|
+
</div>
|
|
1869
|
+
</div>
|
|
1870
|
+
|
|
1871
|
+
${(e?.insights?.length??0)>0?l`
|
|
1872
|
+
<div class="graph-detail-sec">Performance Insights</div>
|
|
1873
|
+
${e.insights.map(s=>l`
|
|
1874
|
+
<div class="graph-detail-finding">
|
|
1875
|
+
<span class="graph-detail-severity graph-detail-severity-${s.severity}">${s.severity}</span>
|
|
1876
|
+
<div class="graph-detail-finding-title">${s.title}</div>
|
|
1877
|
+
<div class="graph-detail-finding-meta">${s.type}</div>
|
|
1878
|
+
</div>
|
|
1879
|
+
`)}
|
|
1880
|
+
`:p}
|
|
1881
|
+
`}renderIssuesTab(t){let e=t?.openIssueCount??0;return e===0?l`<div class="graph-detail-empty">No open issues</div>`:l`
|
|
1882
|
+
<div class="graph-detail-issue-summary">
|
|
1883
|
+
<div class="graph-detail-stat">
|
|
1884
|
+
<div class="graph-detail-val" style="color:${ut}">${e}</div>
|
|
1885
|
+
<div class="graph-detail-lbl">OPEN ISSUES</div>
|
|
1886
|
+
</div>
|
|
1887
|
+
</div>
|
|
1888
|
+
<p class="graph-detail-hint">View the Issues tab for full details and remediation hints.</p>
|
|
1889
|
+
`}getSelectedNodeDetail(){if(!this.locked)return null;let t=this.graphNodes.find(s=>s.id===this.locked);if(!t)return null;let e=this.graphEdges.filter(s=>s.source===this.locked||s.target===this.locked);return {node:t,edges:e}}};u([I({context:A})],M.prototype,"store",2),u([E()],M.prototype,"graphNodes",2),u([E()],M.prototype,"graphEdges",2),u([E()],M.prototype,"locked",2),u([E()],M.prototype,"hovered",2),u([E()],M.prototype,"loading",2),u([E()],M.prototype,"activeLayers",2),u([E()],M.prototype,"searchQuery",2),u([E()],M.prototype,"viewTransform",2),u([E()],M.prototype,"dragging",2),u([E()],M.prototype,"detailTab",2),u([E()],M.prototype,"consolidatedFlows",2),u([E()],M.prototype,"activeFlowIdx",2),u([E()],M.prototype,"focusIdx",2),M=u([$("bk-graph-view")],M);var ie=class extends J{constructor(){super(...arguments);this.activeTab="requests";this.handleNavigateExplorer=t=>{let e=t.detail;ve.some(s=>s.key===e)&&(this.activeTab=e);};}connectedCallback(){super.connectedCallback(),window.addEventListener("navigate-explorer",this.handleNavigateExplorer);}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("navigate-explorer",this.handleNavigateExplorer);}switchTab(t){this.activeTab=t,fetch(`${T.tab}?event=explorer.${t}`).catch(()=>{});}getCount(t){let e=this.store.state;switch(t){case "requests":return e.requests.filter(s=>!s.path?.startsWith(U)).length;case "fetches":return e.fetches.length;case "queries":return e.queries.length;case "logs":return e.logs.length;case "errors":return e.errors.length}}render(){return l`
|
|
1890
|
+
<div class="explorer-tabs">
|
|
1891
|
+
${ve.map(t=>l`
|
|
1892
|
+
<button class="explorer-tab ${this.activeTab===t.key?"active":""}"
|
|
1893
|
+
@click=${()=>this.switchTab(t.key)}>
|
|
1894
|
+
${t.label}
|
|
1895
|
+
<span class="explorer-tab-count">${this.getCount(t.key)}</span>
|
|
1896
|
+
</button>
|
|
1897
|
+
`)}
|
|
1898
|
+
</div>
|
|
1899
|
+
<div style="display:${this.activeTab==="requests"?"block":"none"}">
|
|
1900
|
+
<bk-requests-view></bk-requests-view>
|
|
1901
|
+
</div>
|
|
1902
|
+
<div style="display:${this.activeTab==="fetches"?"block":"none"}">
|
|
1903
|
+
<bk-fetches-view></bk-fetches-view>
|
|
1904
|
+
</div>
|
|
1905
|
+
<div style="display:${this.activeTab==="queries"?"block":"none"}">
|
|
1906
|
+
<bk-queries-view></bk-queries-view>
|
|
1907
|
+
</div>
|
|
1908
|
+
<div style="display:${this.activeTab==="logs"?"block":"none"}">
|
|
1909
|
+
<bk-logs-view></bk-logs-view>
|
|
1910
|
+
</div>
|
|
1911
|
+
<div style="display:${this.activeTab==="errors"?"block":"none"}">
|
|
1912
|
+
<bk-errors-view></bk-errors-view>
|
|
1913
|
+
</div>
|
|
1914
|
+
`}};u([E()],ie.prototype,"activeTab",2),ie=u([$("bk-explorer-view")],ie);var Yi=[{key:"all",label:"All"},{key:"security",label:"Security"},{key:"performance",label:"Performance"},{key:"quality",label:"Quality"}],mt=class extends J{constructor(){super(...arguments);this.filter="all";this.expandedIdx=-1;this.showDismissed=false;}getFilteredIssues(t){return (this.filter==="all"?t:t.filter(s=>Vt(s)===this.filter)).sort((s,i)=>{let n=h=>h==="open"||h==="regressed"?0:h==="fixing"?1:2,a=n(s.state)-n(i.state);if(a!==0)return a;let c=Y[s.issue.severity]?.sort??3,d=Y[i.issue.severity]?.sort??3;return c-d})}getCounts(t){let e={all:t.length,security:0,performance:0,quality:0};for(let s of t)e[Vt(s)]++;return e}render(){if(!(this.store.state.requests.length>0||this.store.state.queries.length>0))return l`<bk-empty-state
|
|
1915
|
+
title="Waiting for requests..."
|
|
1916
|
+
subtitle="Start using your app to see insights here"
|
|
1917
|
+
></bk-empty-state>`;let e=(this.store.state.issues||[]).filter(ir),s=this.getFilteredIssues(e),i=this.getCounts(e),n=s.filter(f=>f.state==="open"&&f.aiStatus!=="wont_fix"),a=s.filter(f=>f.state==="regressed"&&f.aiStatus!=="wont_fix"),c=s.filter(f=>f.state==="fixing"),d=s.filter(f=>f.state==="resolved"),h=s.filter(f=>f.aiStatus==="wont_fix"),m=0;return l`
|
|
1918
|
+
<div class="insights-filters">
|
|
1919
|
+
${Yi.map(f=>l`
|
|
1920
|
+
<button class="insights-chip ${this.filter===f.key?"active":""}"
|
|
1921
|
+
@click=${()=>{this.filter=f.key,this.expandedIdx=-1;}}>
|
|
1922
|
+
${f.label}
|
|
1923
|
+
${i[f.key]>0?l`<span class="insights-chip-count">${i[f.key]}</span>`:p}
|
|
1924
|
+
</button>
|
|
1925
|
+
`)}
|
|
1926
|
+
</div>
|
|
1927
|
+
|
|
1928
|
+
<div class="insights-list">
|
|
1929
|
+
${n.length===0&&a.length===0&&c.length===0&&d.length===0&&h.length===0?l`<div class="insights-empty"><span class="insights-empty-icon">\u2713</span>${this.filter==="all"?"All clear \u2014 no issues detected":`No ${this.filter} issues`}</div>`:p}
|
|
1930
|
+
|
|
1931
|
+
${a.length>0?l`
|
|
1932
|
+
<div class="insights-section insights-section-regressed">
|
|
1933
|
+
<span class="insights-section-icon">\u21A9</span> Regressed
|
|
1934
|
+
<span class="insights-section-count">${a.length}</span>
|
|
1935
|
+
</div>
|
|
1936
|
+
${a.map(f=>this.renderIssueCard(f,m++))}
|
|
1937
|
+
`:p}
|
|
1938
|
+
|
|
1939
|
+
${n.length>0?l`
|
|
1940
|
+
<div class="insights-section">
|
|
1941
|
+
<span class="insights-section-icon">\u25CF</span> Open
|
|
1942
|
+
<span class="insights-section-count">${n.length}</span>
|
|
1943
|
+
</div>
|
|
1944
|
+
${n.map(f=>this.renderIssueCard(f,m++))}
|
|
1945
|
+
`:p}
|
|
1946
|
+
|
|
1947
|
+
${c.length>0?l`
|
|
1948
|
+
<div class="insights-section insights-section-verifying">
|
|
1949
|
+
<span class="insights-section-icon">\u29D7</span> Verifying
|
|
1950
|
+
<span class="insights-section-count">${c.length}</span>
|
|
1951
|
+
</div>
|
|
1952
|
+
${c.map(f=>this.renderIssueCard(f,m++))}
|
|
1953
|
+
`:p}
|
|
1954
|
+
|
|
1955
|
+
${d.length>0?l`
|
|
1956
|
+
<div class="insights-section insights-section-resolved">
|
|
1957
|
+
<span class="insights-section-icon">\u2713</span> Resolved
|
|
1958
|
+
<span class="insights-section-count">${d.length}</span>
|
|
1959
|
+
</div>
|
|
1960
|
+
${d.map(f=>this.renderIssueCard(f,m++))}
|
|
1961
|
+
`:p}
|
|
1962
|
+
|
|
1963
|
+
${h.length>0?l`
|
|
1964
|
+
<div class="insights-section insights-section-dismissed" @click=${()=>{this.showDismissed=!this.showDismissed;}}>
|
|
1965
|
+
<span class="insights-section-icon">${this.showDismissed?"\u25BE":"\u25B8"}</span> Won't Fix
|
|
1966
|
+
<span class="insights-section-count">${h.length}</span>
|
|
1967
|
+
</div>
|
|
1968
|
+
${this.showDismissed?h.map(f=>this.renderIssueCard(f,m++)):p}
|
|
1969
|
+
`:p}
|
|
1970
|
+
</div>
|
|
1971
|
+
`}renderIssueCard(t,e){let s=t.issue,i=Y[s.severity]||Y.info,n=this.expandedIdx===e,a=t.state==="resolved",c=t.state==="fixing",d=Vt(t);return l`
|
|
1972
|
+
<div class="insights-card ${n?"expanded":""} ${a?"resolved":""}"
|
|
1973
|
+
@click=${()=>{this.expandedIdx=this.expandedIdx===e?-1:e;}}>
|
|
1974
|
+
<div class="insights-card-left">
|
|
1975
|
+
<span class="insights-sev ${i.cls}">${i.icon}</span>
|
|
1976
|
+
</div>
|
|
1977
|
+
<div class="insights-card-body">
|
|
1978
|
+
<div class="insights-card-header">
|
|
1979
|
+
<span class="insights-card-title ${a?"resolved":""}">${s.title}</span>
|
|
1980
|
+
<span class="insights-card-cat">${d}</span>
|
|
1981
|
+
${s.count?l`<span class="insights-card-count">${s.count}\u00D7</span>`:p}
|
|
1982
|
+
${t.state==="regressed"?l`<span class="insights-badge-regressed">regressed</span>`:p}
|
|
1983
|
+
${c?l`<span class="insights-badge-verifying">verifying</span>`:p}
|
|
1984
|
+
${a?l`<span class="insights-badge-resolved">resolved</span>`:p}
|
|
1985
|
+
</div>
|
|
1986
|
+
<div class="insights-card-desc">${s.desc}</div>
|
|
1987
|
+
${s.detail?l`<div class="insights-card-detail">${s.detail}</div>`:p}
|
|
1988
|
+
${t.cleanHitsSinceLastSeen>0?l`
|
|
1989
|
+
<div class="insights-card-progress">${t.cleanHitsSinceLastSeen}/${5} clean requests</div>
|
|
1990
|
+
`:p}
|
|
1991
|
+
${n&&s.hint?l`<div class="insights-card-hint">${s.hint}</div>`:p}
|
|
1992
|
+
</div>
|
|
1993
|
+
${s.hint?l`<span class="insights-card-arrow">${n?"\u2193":"\u2192"}</span>`:p}
|
|
1994
|
+
</div>
|
|
1995
|
+
`}};u([E()],mt.prototype,"filter",2),u([E()],mt.prototype,"expandedIdx",2),u([E()],mt.prototype,"showDismissed",2),mt=u([$("bk-insights-view")],mt);function Vi(o){return o===0?"<1ms":y(o)}var N=class extends b{constructor(){super(...arguments);this.requestId="";this.requestStarted=0;this.data=null;this.loading=false;this.failed=false;this.expandedSqlIdx=-1;}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.store.addEventListener("state-changed",()=>this.requestUpdate()),this.requestId&&this.loadTimeline();}async loadTimeline(){if(!this.requestId)return;let t=N.cache.get(this.requestId);if(t){this.data=t;return}this.loading=true;try{let e=await fetch(`${T.activity}?requestId=${this.requestId}`);if(!e.ok){this.failed=!0,this.loading=!1;return}let s=await e.json();if(N.cache.size>=We){let i=N.cache.keys().next().value;i!==void 0&&N.cache.delete(i);}N.cache.set(this.requestId,s),this.data=s,this.loading=!1;}catch(e){console.debug("[brakit] timeline load failed:",e),this.failed=true,this.loading=false;}}toggleSql(t,e){e.stopPropagation(),this.expandedSqlIdx=this.expandedSqlIdx===t?-1:t;}copySql(t,e){e.stopPropagation(),navigator.clipboard.writeText(t).then(()=>D.show("SQL copied")).catch(()=>D.show("Copy failed"));}render(){if(this.loading)return l`<div class="tl-loading">Loading activity...</div>`;if(this.failed||!this.data||this.data.total===0)return p;let t=this.data,e=t.timeline[0]?.timestamp??0;return l`
|
|
1484
1996
|
<div class="tl-header">
|
|
1485
1997
|
<span class="tl-title">Activity Timeline</span>
|
|
1486
1998
|
<span class="tl-counts">
|
|
1487
|
-
${t.counts.queries>0?
|
|
1488
|
-
${t.counts.fetches>0?
|
|
1489
|
-
${t.counts.logs>0?
|
|
1490
|
-
${t.counts.errors>0?
|
|
1999
|
+
${t.counts.queries>0?l`<span class="tl-count tl-count-query">${t.counts.queries} quer${t.counts.queries===1?"y":"ies"}</span>`:p}
|
|
2000
|
+
${t.counts.fetches>0?l`<span class="tl-count tl-count-fetch">${t.counts.fetches} fetch${t.counts.fetches===1?"":"es"}</span>`:p}
|
|
2001
|
+
${t.counts.logs>0?l`<span class="tl-count tl-count-log">${t.counts.logs} log${t.counts.logs===1?"":"s"}</span>`:p}
|
|
2002
|
+
${t.counts.errors>0?l`<span class="tl-count tl-count-error">${t.counts.errors} error${t.counts.errors===1?"":"s"}</span>`:p}
|
|
1491
2003
|
</span>
|
|
1492
2004
|
</div>
|
|
1493
|
-
<div class="tl-events">${this.renderTimeline(t.timeline,
|
|
1494
|
-
`}renderTimeline(t,
|
|
1495
|
-
${this.renderEvent(
|
|
2005
|
+
<div class="tl-events">${this.renderTimeline(t.timeline,e)}</div>
|
|
2006
|
+
`}renderTimeline(t,e){let s=new Map,i=[];for(let a of t){let c=a.type==="query"?a.data.parentFetchId:void 0;if(a.type==="query"&&c){let d=s.get(c);d||(d=[],s.set(c,d)),d.push(a);}else i.push(a);}let n=0;return i.map(a=>{let c=n++,d=a.type==="fetch"?a.data.fetchId:void 0,h=d?s.get(d):void 0;if(h&&h.length>0){let m=h.length;return l`
|
|
2007
|
+
${this.renderEvent(a,c,e)}
|
|
1496
2008
|
<div class="tl-nested">
|
|
1497
2009
|
<span class="tl-nested-label">${m} nested quer${m===1?"y":"ies"}</span>
|
|
1498
|
-
${h.map(
|
|
2010
|
+
${h.map(f=>{let S=n++;return this.renderEvent(f,S,e,true)})}
|
|
1499
2011
|
</div>
|
|
1500
|
-
`}return this.renderEvent(
|
|
1501
|
-
<div class="tl-event ${h?"tl-clickable":""} ${
|
|
2012
|
+
`}return this.renderEvent(a,c,e)})}renderEvent(t,e,s,i=false){let n=Bs[t.type]||"var(--text-dim)",a=Ws[t.type]||t.type,c="+"+y(Math.round(t.timestamp-s)),d=t.type==="query"?t.data.sql:void 0,h=!!d,m=this.expandedSqlIdx===e;return l`
|
|
2013
|
+
<div class="tl-event ${h?"tl-clickable":""} ${i?"tl-nested-event":""}"
|
|
1502
2014
|
style="${h?"":`border-left-color:${n}`}"
|
|
1503
|
-
@click=${h?
|
|
2015
|
+
@click=${h?f=>this.toggleSql(e,f):p}>
|
|
1504
2016
|
<span class="tl-event-time">${c}</span>
|
|
1505
|
-
<span class="tl-event-type" style="color:${n}">${
|
|
2017
|
+
<span class="tl-event-type" style="color:${n}">${a}</span>
|
|
1506
2018
|
${this.renderEventContent(t)}
|
|
1507
|
-
${
|
|
2019
|
+
${d?l`
|
|
1508
2020
|
<div class="tl-event-sql ${m?"open":""}">
|
|
1509
|
-
<button class="tl-sql-copy" @click=${
|
|
1510
|
-
${
|
|
1511
|
-
</div>`:
|
|
1512
|
-
</div>
|
|
1513
|
-
`}renderEventContent(t){switch(t.type){case "fetch":{let
|
|
1514
|
-
<span class="tl-event-summary">${
|
|
1515
|
-
<span class="tl-event-status" style="${
|
|
1516
|
-
<span class="tl-event-dur">${
|
|
1517
|
-
`}case "query":{let
|
|
1518
|
-
<span class="tl-event-summary"><span style="color:${n};font-weight:600">${
|
|
1519
|
-
<span class="tl-event-dur">${
|
|
1520
|
-
`}case "log":{let
|
|
2021
|
+
<button class="tl-sql-copy" @click=${f=>this.copySql(d,f)}>Copy</button>
|
|
2022
|
+
${d}
|
|
2023
|
+
</div>`:p}
|
|
2024
|
+
</div>
|
|
2025
|
+
`}renderEventContent(t){switch(t.type){case "fetch":{let e=t.data,s=e.statusCode>=400;return l`
|
|
2026
|
+
<span class="tl-event-summary">${e.method} ${e.url}</span>
|
|
2027
|
+
<span class="tl-event-status" style="${s?"color:var(--red)":""}">${e.statusCode}</span>
|
|
2028
|
+
<span class="tl-event-dur">${y(e.durationMs)}</span>
|
|
2029
|
+
`}case "query":{let e=t.data,s=(e.normalizedOp||e.operation||"?").toUpperCase(),i=e.table||e.model||"",n=Ee[s]||"var(--text-dim)";return l`
|
|
2030
|
+
<span class="tl-event-summary"><span style="color:${n};font-weight:600">${s}</span> ${i}</span>
|
|
2031
|
+
<span class="tl-event-dur">${Vi(e.durationMs)}</span>
|
|
2032
|
+
`}case "log":{let e=t.data,s=qs[e.level]||"var(--text-dim)";return l`<span class="tl-event-summary"><span style="color:${s}">${e.level.toUpperCase()}</span> ${e.message}</span>`}case "error":{let e=t.data;return l`<span class="tl-event-summary" style="color:var(--red)">${e.name}: ${e.message}</span>`}default:return p}}};N.cache=new Map,u([I({context:A})],N.prototype,"store",2),u([L({attribute:"request-id"})],N.prototype,"requestId",2),u([L({attribute:"request-started",type:Number})],N.prototype,"requestStarted",2),u([E()],N.prototype,"data",2),u([E()],N.prototype,"loading",2),u([E()],N.prototype,"failed",2),u([E()],N.prototype,"expandedSqlIdx",2),N=u([$("bk-timeline-panel")],N);function Dt(o){try{return JSON.parse(o)}catch{return null}}var Re=class{constructor(r,t){this.host=r;this.store=t;this.retryCount=0;this.boundHandlers={fetch:r=>{let t=Dt(r.data);t&&this.store.prependFetch(t);},log:r=>{let t=Dt(r.data);t&&this.store.prependLog(t);},error:r=>{let t=Dt(r.data);t&&this.store.prependError(t);},query:r=>{let t=Dt(r.data);t&&this.store.prependQuery(t);},issues:r=>{let t=Dt(r.data);t&&this.store.setIssues(t);}};r.addController(this);}hostConnected(){this.connect();}hostDisconnected(){this.removeListeners(),this.eventSource?.close(),clearTimeout(this.reloadTimer),clearTimeout(this.perfReloadTimer),clearTimeout(this.reconnectTimer);}removeListeners(){this.eventSource&&(this.eventSource.removeEventListener(he,this.boundHandlers.fetch),this.eventSource.removeEventListener("log",this.boundHandlers.log),this.eventSource.removeEventListener(ue,this.boundHandlers.error),this.eventSource.removeEventListener(me,this.boundHandlers.query),this.eventSource.removeEventListener(fe,this.boundHandlers.issues));}connect(){this.removeListeners(),this.eventSource?.close(),this.eventSource=new EventSource(T.events),this.eventSource.onopen=()=>{this.retryCount=0;},this.eventSource.onerror=()=>{this.eventSource?.close(),this.scheduleReconnect();},this.eventSource.onmessage=r=>{let t=Dt(r.data);t&&(t.path?.startsWith(U)||(this.store.prependRequest(t),clearTimeout(this.reloadTimer),this.reloadTimer=setTimeout(()=>this.reloadFlows(),300),this.store.state.activeView==="performance"&&(clearTimeout(this.perfReloadTimer),this.perfReloadTimer=setTimeout(()=>this.reloadMetrics(),Qe))));},this.eventSource.addEventListener(he,this.boundHandlers.fetch),this.eventSource.addEventListener("log",this.boundHandlers.log),this.eventSource.addEventListener(ue,this.boundHandlers.error),this.eventSource.addEventListener(me,this.boundHandlers.query),this.eventSource.addEventListener(fe,this.boundHandlers.issues);}scheduleReconnect(){if(this.retryCount>=10)return;let r=Math.min(1e3*2**this.retryCount,3e4);this.retryCount++,this.reconnectTimer=setTimeout(()=>this.connect(),r);}async reloadFlows(){try{let t=await(await fetch(T.flows)).json();this.store.setFlows(t.flows);}catch{}}async reloadMetrics(){try{let t=await(await fetch(T.metricsLive)).json();this.store.setMetrics(t.endpoints||[]);}catch{}}};function Ar(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>`}function Ir(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`}function Cr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>`}function Lr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2z"/></svg>`}function Mr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`}function Nr(){return l`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="18" r="3"/><line x1="9" y1="6" x2="15" y2="6"/><line x1="6" y1="9" x2="6" y2="15"/><line x1="18" y1="9" x2="18" y2="15"/><line x1="9" y1="18" x2="15" y2="18"/></svg>`}var ft=class extends b{constructor(){super(...arguments);this.store=new ye;this.activeView="overview";this.viewMode="simple";this.sse=new Re(this,this.store);this.handleStateChanged=t=>{t.detail==="activeView"&&(this.activeView=this.store.state.activeView),this.requestUpdate();};}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),this.loadInitialData(),this.store.addEventListener("state-changed",this.handleStateChanged);}disconnectedCallback(){super.disconnectedCallback(),this.store.removeEventListener("state-changed",this.handleStateChanged);}async loadInitialData(){try{let[t,e]=await Promise.all([Q(T.flows),Q(T.requests)]);this.store.setFlows(t.flows),this.store.setRequests(e.requests);}catch(t){console.warn("[brakit]",t);}try{let[t,e,s,i,n]=await Promise.all([Q(T.fetches),Q(T.errors),Q(T.logs),Q(T.queries),Q(T.metricsLive)]);this.store.setFetches(t.entries),this.store.setErrors(e.entries),this.store.setLogs(s.entries),this.store.setQueries(i.entries),this.store.setMetrics(n.endpoints||[]);}catch(t){console.warn("[brakit]",t);}try{let t=await Q(T.insights);this.store.setIssues(t.issues||[]);}catch(t){console.warn("[brakit]",t);}}switchView(t){t!==this.activeView&&(this.activeView=t,this.store.setActiveView(t),fetch(`${T.tab}?tab=${encodeURIComponent(t)}`).catch(()=>{}),t==="performance"&&this.sse.reloadMetrics());}async handleClear(){confirm(O.CLEAR_CONFIRM)&&(await fetch(T.clear,{method:"POST"}),this.store.clearAll(),D.show(O.CLEARED_TOAST));}handleCopyAsCurl(t){Lt(t);}render(){let t=this.store.state,e=t.requests.filter(c=>!c.path?.startsWith(U)),s=e.filter(c=>c.statusCode>=400).length,i=e.length>0?Math.round(e.reduce((c,d)=>c+d.durationMs,0)/e.length):0,n=(t.issues||[]).filter($e).length,a=window.__BRAKIT_CONFIG__;return l`
|
|
1521
2033
|
<div class="app" id="app">
|
|
1522
2034
|
<aside class="sidebar">
|
|
1523
2035
|
<div class="sidebar-logo">
|
|
1524
2036
|
<span class="logo-text">brakit</span>
|
|
1525
|
-
<span class="logo-version">v${
|
|
2037
|
+
<span class="logo-version">v${a?.version??""}</span>
|
|
1526
2038
|
</div>
|
|
1527
2039
|
<nav class="sidebar-nav">
|
|
1528
|
-
${this.renderSidebarItem("overview","Overview",
|
|
1529
|
-
|
|
1530
|
-
${this.renderSidebarItem("
|
|
1531
|
-
${this.renderSidebarItem("
|
|
1532
|
-
${this.renderSidebarItem("
|
|
1533
|
-
<div class="sidebar-
|
|
1534
|
-
${this.renderSidebarItem("
|
|
1535
|
-
${this.renderSidebarItem("errors","Errors",Ts(),t.errors.length)}
|
|
1536
|
-
${this.renderSidebarItem("logs","Logs",$s(),t.logs.length)}
|
|
1537
|
-
${this.renderSidebarItem("security","Security",ys(),n,n===0)}
|
|
1538
|
-
${this.renderSidebarItem("performance","Performance",xs(),void 0)}
|
|
2040
|
+
${this.renderSidebarItem("overview","Overview",Ar(),void 0)}
|
|
2041
|
+
${this.renderSidebarItem("actions","Actions",Ir(),t.flows.length)}
|
|
2042
|
+
${this.renderSidebarItem("insights","Insights",Lr(),n,n===0)}
|
|
2043
|
+
${this.renderSidebarItem("performance","Performance",Cr(),void 0)}
|
|
2044
|
+
${this.renderSidebarItem("graph","Graph",Nr(),void 0)}
|
|
2045
|
+
<div class="sidebar-divider"></div>
|
|
2046
|
+
${this.renderSidebarItem("explorer","Explorer",Mr(),e.length+t.fetches.length+t.queries.length+t.logs.length+t.errors.length)}
|
|
1539
2047
|
</nav>
|
|
1540
|
-
<div class="sidebar-footer">:${
|
|
2048
|
+
<div class="sidebar-footer">:${a?.port??""}</div>
|
|
1541
2049
|
</aside>
|
|
1542
2050
|
<div class="main-panel">
|
|
1543
2051
|
<div class="header">
|
|
1544
2052
|
<div class="header-left">
|
|
1545
|
-
<span class="header-title" id="header-title">${
|
|
1546
|
-
<span class="header-sub" id="header-sub">${
|
|
2053
|
+
<span class="header-title" id="header-title">${Pe[this.activeView]||this.activeView}</span>
|
|
2054
|
+
<span class="header-sub" id="header-sub">${He[this.activeView]||""}</span>
|
|
1547
2055
|
</div>
|
|
1548
2056
|
<div class="header-right">
|
|
1549
|
-
${this.activeView==="actions"?
|
|
2057
|
+
${this.activeView==="actions"?l`
|
|
1550
2058
|
<div class="segmented-control" id="mode-toggle">
|
|
1551
2059
|
<button class="segmented-btn ${this.viewMode==="simple"?"active":""}" @click=${()=>{this.viewMode="simple",this.store.setViewMode("simple");}}>Quick</button>
|
|
1552
2060
|
<button class="segmented-btn ${this.viewMode==="detailed"?"active":""}" @click=${()=>{this.viewMode="detailed",this.store.setViewMode("detailed");}}>Detailed</button>
|
|
1553
2061
|
</div>
|
|
1554
|
-
`:
|
|
2062
|
+
`:p}
|
|
1555
2063
|
<button class="btn btn-danger" @click=${this.handleClear}>Clear</button>
|
|
1556
2064
|
</div>
|
|
1557
2065
|
</div>
|
|
1558
2066
|
<div class="main-content">
|
|
1559
|
-
<div
|
|
2067
|
+
<div style="display:${this.activeView==="overview"?"block":"none"}">
|
|
1560
2068
|
<bk-overview-view></bk-overview-view>
|
|
1561
2069
|
</div>
|
|
1562
|
-
<div
|
|
2070
|
+
<div style="display:${this.activeView==="actions"?"block":"none"}">
|
|
1563
2071
|
<bk-flows-view></bk-flows-view>
|
|
1564
2072
|
</div>
|
|
1565
|
-
<div
|
|
1566
|
-
<bk-
|
|
1567
|
-
</div>
|
|
1568
|
-
<div class="view-telemetry" id="fetch-container" style="display:${this.activeView==="fetches"?"block":"none"}">
|
|
1569
|
-
<bk-fetches-view></bk-fetches-view>
|
|
1570
|
-
</div>
|
|
1571
|
-
<div class="view-telemetry" id="query-container" style="display:${this.activeView==="queries"?"block":"none"}">
|
|
1572
|
-
<bk-queries-view></bk-queries-view>
|
|
2073
|
+
<div style="display:${this.activeView==="insights"?"block":"none"}">
|
|
2074
|
+
<bk-insights-view></bk-insights-view>
|
|
1573
2075
|
</div>
|
|
1574
|
-
<div
|
|
1575
|
-
<bk-
|
|
1576
|
-
</div>
|
|
1577
|
-
<div class="view-telemetry" id="log-container" style="display:${this.activeView==="logs"?"block":"none"}">
|
|
1578
|
-
<bk-logs-view></bk-logs-view>
|
|
2076
|
+
<div style="display:${this.activeView==="performance"?"block":"none"}">
|
|
2077
|
+
<bk-performance-view></bk-performance-view>
|
|
1579
2078
|
</div>
|
|
1580
|
-
<div
|
|
1581
|
-
<bk-
|
|
2079
|
+
<div style="display:${this.activeView==="graph"?"block":"none"}">
|
|
2080
|
+
<bk-graph-view></bk-graph-view>
|
|
1582
2081
|
</div>
|
|
1583
|
-
<div
|
|
1584
|
-
<bk-
|
|
2082
|
+
<div style="display:${this.activeView==="explorer"?"block":"none"}">
|
|
2083
|
+
<bk-explorer-view></bk-explorer-view>
|
|
1585
2084
|
</div>
|
|
1586
2085
|
</div>
|
|
1587
2086
|
<div class="footer">
|
|
1588
|
-
<span id="stat-total">${
|
|
1589
|
-
<span id="stat-flows">${t.flows.length}
|
|
1590
|
-
<span id="stat-errors" class="error-count">${
|
|
1591
|
-
<span id="stat-avg">Avg: ${
|
|
2087
|
+
<span id="stat-total">${e.length} ${B(e.length,"request")}</span>
|
|
2088
|
+
<span id="stat-flows">${t.flows.length} ${B(t.flows.length,"action")}</span>
|
|
2089
|
+
<span id="stat-errors" class="error-count">${s} ${B(s,"error")}</span>
|
|
2090
|
+
<span id="stat-avg">Avg: ${i}ms</span>
|
|
1592
2091
|
</div>
|
|
1593
2092
|
</div>
|
|
1594
2093
|
</div>
|
|
1595
2094
|
<bk-toast></bk-toast>
|
|
1596
|
-
`}renderSidebarItem(t,s,
|
|
2095
|
+
`}renderSidebarItem(t,e,s,i,n=false){return l`
|
|
1597
2096
|
<button class="sidebar-item ${this.activeView===t?"active":""}" @click=${()=>this.switchView(t)}>
|
|
1598
|
-
<span class="item-icon">${
|
|
1599
|
-
<span class="item-label">${
|
|
1600
|
-
${
|
|
2097
|
+
<span class="item-icon">${s}</span>
|
|
2098
|
+
<span class="item-label">${e}</span>
|
|
2099
|
+
${i!==void 0?l`<span class="item-count" style="display:${n?"none":""}">${i}</span>`:p}
|
|
1601
2100
|
</button>
|
|
1602
|
-
`}};u([
|
|
2101
|
+
`}};u([Je({context:A})],ft.prototype,"store",2),u([E()],ft.prototype,"activeView",2),u([E()],ft.prototype,"viewMode",2),ft=u([$("bk-dashboard")],ft);
|
|
1603
2102
|
/*! Bundled license information:
|
|
1604
2103
|
|
|
1605
2104
|
@lit/reactive-element/css-tag.js:
|
|
@@ -1622,6 +2121,8 @@ lit-element/lit-element.js:
|
|
|
1622
2121
|
@lit/reactive-element/decorators/query-async.js:
|
|
1623
2122
|
@lit/reactive-element/decorators/query-assigned-nodes.js:
|
|
1624
2123
|
@lit/context/lib/decorators/provide.js:
|
|
2124
|
+
lit-html/directive.js:
|
|
2125
|
+
lit-html/directives/unsafe-html.js:
|
|
1625
2126
|
(**
|
|
1626
2127
|
* @license
|
|
1627
2128
|
* Copyright 2017 Google LLC
|