nestjs-devtools 1.4.0 → 1.5.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.
@@ -3,25 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PROFILER_PAGE = void 0;
4
4
  const PROFILER_PAGE = (config) => {
5
5
  const { theme = 'dark' } = config ?? {};
6
- const dark = theme === 'dark';
7
- const bg = dark ? '#0f172a' : '#f8fafc';
8
- const fg = dark ? '#e2e8f0' : '#1e293b';
9
- const cardBg = dark ? '#1e293b' : '#ffffff';
10
- const borderClr = dark ? '#334155' : '#e2e8f0';
11
- const headerBg = dark ? '#0f172a' : '#ffffff';
12
- const dimText = dark ? '#64748b' : '#94a3b8';
13
- const dimmerText = dark ? '#475569' : '#cbd5e1';
14
- const accentText = dark ? '#38bdf8' : '#0284c7';
15
- const labelFg = dark ? '#f1f5f9' : '#0f172a';
16
- const navBg = dark ? '#1e293b' : '#f1f5f9';
17
- const navFg = dark ? '#94a3b8' : '#475569';
18
- const I = {
19
- req: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M5 8h6M8 5l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
20
- perf: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M8 4v4l2.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
21
- db: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><ellipse cx="8" cy="4.5" rx="5" ry="2" stroke="currentColor" stroke-width="1.5"/><path d="M3 4.5v7c0 1.1 2.2 2 5 2s5-.9 5-2v-7" stroke="currentColor" stroke-width="1.5"/><path d="M3 8c0 1.1 2.2 2 5 2s5-.9 5-2" stroke="currentColor" stroke-width="1.5"/></svg>',
22
- routes: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
23
- log: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.5"/><path d="M5 6h6M5 9h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
24
- };
25
6
  return `<!DOCTYPE html>
26
7
  <html lang="en">
27
8
  <head>
@@ -30,224 +11,241 @@ const PROFILER_PAGE = (config) => {
30
11
  <title>NestJS Devtools Profiler</title>
31
12
  <style>
32
13
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
14
+ html, body { height: 100%; }
33
15
  body {
34
- font-family: Inter,SF Pro,system-ui,-apple-system,sans-serif;
35
- background: ${bg}; color: ${fg}; font-size: 13px; line-height: 1.6;
36
- min-height: 100vh;
16
+ font-family: -apple-system,BlinkMacSystemFont,sans-serif;
17
+ background: #f5f5f5; color: #222; font-size: 13px; line-height: 1.5;
37
18
  }
38
- .header {
39
- background: ${headerBg}; border-bottom: 1px solid ${borderClr};
40
- padding: 16px 28px; display: flex; align-items: center; gap: 14px;
41
- position: sticky; top: 0; z-index: 10;
19
+ .toolbar {
20
+ background: #1a1a1a; color: #ccc; display: flex; align-items: center;
21
+ height: 30px; padding: 0 10px; font-size: 11px;
22
+ box-shadow: 0 1px 4px rgba(0,0,0,0.3); position: sticky; top: 0; z-index: 100;
42
23
  }
43
- .header h1 { font-size: 17px; font-weight: 700; color: ${accentText}; letter-spacing: -0.3px; }
44
- .header span { color: ${dimText}; font-size: 13px; }
45
- .container { max-width: 1200px; margin: 0 auto; padding: 24px 28px; }
46
- .nav { display: flex; gap: 4px; margin-bottom: 24px; background: ${navBg}; border-radius: 8px; padding: 3px; display: inline-flex; }
47
- .nav button {
48
- padding: 7px 16px; border-radius: 6px; border: none;
49
- background: transparent; color: ${navFg}; cursor: pointer;
50
- font-size: 13px; font-weight: 500; transition: all .12s ease;
24
+ .toolbar .brand { font-weight: 700; font-size: 12px; color: #888; padding: 0 12px 0 4px; }
25
+ .toolbar .title { color: #aaa; font-size: 11px; }
26
+ .toolbar .nav { margin-left: auto; display: flex; align-items: center; height: 100%; }
27
+ .toolbar .nav a {
28
+ display: inline-flex; align-items: center; height: 100%; padding: 0 10px;
29
+ color: #888; text-decoration: none; font-size: 11px; cursor: pointer;
30
+ border-left: 1px solid #333; transition: background .1s;
51
31
  }
52
- .nav button.active { background: ${cardBg}; color: ${labelFg}; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
53
- .nav button:hover:not(.active) { color: ${labelFg}; }
54
- .section { display: none; }
55
- .section.active { display: block; }
56
- .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; }
57
- .card {
58
- background: ${cardBg}; border-radius: 8px; border: 1px solid ${borderClr};
59
- overflow: hidden; cursor: pointer; transition: border-color .15s ease;
32
+ .toolbar .nav a:hover { background: #333; color: #ddd; }
33
+ .toolbar .nav a.active { color: #fff; background: #333; }
34
+ .layout { display: flex; min-height: calc(100vh - 30px); }
35
+ .sidebar {
36
+ width: 240px; background: #fff; border-right: 1px solid #ddd;
37
+ padding: 16px; flex-shrink: 0; font-size: 12px;
60
38
  }
61
- .card:hover { border-color: ${accentText}; }
62
- .card-header {
63
- padding: 12px 14px; display: flex; align-items: center; gap: 9px;
64
- user-select: none;
39
+ .sidebar h3 { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; color: #999; margin-bottom: 10px; }
40
+ .sidebar .metric { display: flex; justify-content: space-between; padding: 4px 0; font-size: 12px; }
41
+ .sidebar .metric .label { color: #888; }
42
+ .sidebar .metric .value { color: #222; font-weight: 500; text-align: right; }
43
+ .sidebar .section { margin-bottom: 16px; }
44
+ .sidebar .badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; }
45
+ .badge-get { background: #d4edda; color: #155724; }
46
+ .badge-post { background: #cce5ff; color: #004085; }
47
+ .badge-put { background: #fff3cd; color: #856404; }
48
+ .badge-delete { background: #f8d7da; color: #721c24; }
49
+ .badge-patch { background: #e8daef; color: #6c3483; }
50
+ .main { flex: 1; padding: 20px; }
51
+ .panel { background: #fff; border: 1px solid #ddd; margin-bottom: 12px; border-radius: 0; }
52
+ .panel-header {
53
+ padding: 8px 12px; display: flex; align-items: center; gap: 8px;
54
+ cursor: pointer; user-select: none; font-size: 12px; font-weight: 600;
55
+ border-bottom: 1px solid #eee; color: #333;
65
56
  }
66
- .card-header .icon { color: ${accentText}; width: 16px; height: 16px; flex-shrink: 0; }
67
- .card-header .label { font-weight: 600; font-size: 13px; color: ${labelFg}; }
68
- .card-header .summary { color: ${dimText}; font-size: 11px; margin-left: auto; }
69
- .card-body { max-height: 0; overflow: hidden; transition: max-height .25s ease; }
70
- .card-body.open { max-height: 2000px; }
71
- .card-body-inner { padding: 0 14px 14px; }
72
- .card-body table { width: 100%; border-collapse: collapse; font-size: 12px; }
73
- .card-body td, .card-body th {
74
- padding: 6px 8px; border-bottom: 1px solid ${borderClr}; text-align: left;
75
- vertical-align: top; word-break: break-word;
76
- }
77
- .card-body th { color: ${dimText}; font-weight: 500; width: 100px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px; }
78
- .card-body tr:last-child td, .card-body tr:last-child th { border-bottom: none; }
79
- .badge {
80
- display: inline-block; padding: 2px 7px; border-radius: 4px;
81
- font-size: 10px; font-weight: 600; letter-spacing: 0.2px;
82
- }
83
- .badge-get { background: #065f4630; color: #34d399; }
84
- .badge-post { background: #1e3a5f30; color: #93c5fd; }
85
- .badge-put { background: #713f1230; color: #fcd34d; }
86
- .badge-delete { background: #7f1d1d30; color: #fca5a5; }
87
- .badge-patch { background: #581c8730; color: #d8b4fe; }
88
- .badge-request { background: #0c4a6e30; color: #7dd3fc; }
89
- .empty { padding: 20px; text-align: center; color: ${dimmerText}; font-size: 13px; }
90
- .mono { font-family: ui-monospace,SF Mono,Menlo,monospace; font-size: 11px; }
91
- .history-wrap { background: ${cardBg}; border: 1px solid ${borderClr}; border-radius: 8px; overflow: hidden; }
92
- .history-table { width: 100%; border-collapse: collapse; font-size: 12px; }
93
- .history-table th { padding: 10px 14px; text-align: left; color: ${dimText}; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px; border-bottom: 1px solid ${borderClr}; background: ${dark ? '#0f172a' : '#f8fafc'}; }
94
- .history-table td { padding: 10px 14px; border-bottom: 1px solid ${borderClr}; }
95
- .history-table tr:last-child td { border-bottom: none; }
96
- .history-table tr:hover td { background: ${dark ? '#33415520' : '#f1f5f9'}; }
97
- .error-text { color: #f87171; font-weight: 600; }
98
- .warn-text { color: #fbbf24; font-weight: 600; }
99
- .ok-text { color: #34d399; font-weight: 600; }
100
- .qlabel { display: inline-block; width: 18px; height: 18px; line-height: 18px; text-align: center; border-radius: 3px; font-size: 10px; font-weight: 700; margin-right: 6px; color: #fff; flex-shrink: 0; }
101
- .qlabel-1 { background: #3b82f6; }
102
- .qlabel-2 { background: #8b5cf6; }
103
- .qlabel-3 { background: #10b981; }
104
- .query-row { display: flex; align-items: flex-start; padding: 6px 0; gap: 6px; border-bottom: 1px solid ${borderClr}; }
57
+ .panel-header:hover { background: #fafafa; }
58
+ .panel-header .summary { color: #999; font-weight: 400; font-size: 11px; margin-left: auto; }
59
+ .panel-body { display: none; padding: 10px 12px 12px; font-size: 12px; }
60
+ .panel-body.open { display: block; }
61
+ .panel-body table { width: 100%; border-collapse: collapse; }
62
+ .panel-body td, .panel-body th { padding: 4px 8px; text-align: left; vertical-align: top; border-bottom: 1px solid #eee; font-size: 12px; }
63
+ .panel-body th { color: #888; font-weight: 500; width: 100px; }
64
+ .panel-body tr:last-child td, .panel-body tr:last-child th { border-bottom: none; }
65
+ .empty { color: #bbb; font-size: 12px; }
66
+ .mono { font-family: SF Mono,Menlo,monospace; font-size: 11px; }
67
+ .query-table { width: 100%; }
68
+ .query-row { display: flex; align-items: flex-start; padding: 5px 0; gap: 6px; border-bottom: 1px solid #eee; font-size: 11px; }
105
69
  .query-row:last-child { border-bottom: none; }
106
- .query-sql { flex: 1; min-width: 0; }
107
- .query-time { color: ${dark ? '#fbbf24' : '#b45309'}; white-space: nowrap; font-family: ui-monospace,SF Mono,monospace; font-size: 11px; }
108
- .log-row { padding: 5px 0; border-bottom: 1px solid ${borderClr}; display: flex; align-items: flex-start; gap: 8px; }
70
+ .query-num { color: #999; width: 22px; flex-shrink: 0; }
71
+ .query-sql { font-family: SF Mono,Menlo,monospace; word-break: break-all; flex: 1; }
72
+ .query-time { color: #b8860b; white-space: nowrap; font-family: SF Mono,Menlo,monospace; flex-shrink: 0; }
73
+ .log-row { display: flex; align-items: flex-start; gap: 8px; padding: 3px 0; border-bottom: 1px solid #eee; font-size: 11px; }
109
74
  .log-row:last-child { border-bottom: none; }
110
- .log-level { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.3px; white-space: nowrap; }
111
- .route-group { padding: 6px 0; }
112
- .route-group-header { color: ${accentText}; font-weight: 600; font-size: 11px; padding: 4px 0; }
113
- .route-item { padding: 2px 0 2px 14px; font-family: ui-monospace,SF Mono,monospace; font-size: 11px; }
114
- .route-controller { color: ${dimText}; }
115
- .exception-box { background: ${dark ? 'rgba(239,68,68,0.08)' : 'rgba(239,68,68,0.04)'}; border-radius: 6px; padding: 10px; }
116
- .exception-box .msg { color: #f87171; font-weight: 600; font-size: 12px; margin-bottom: 6px; }
117
- .exception-box .stack { font-family: ui-monospace,SF Mono,monospace; font-size: 11px; color: ${dimText}; white-space: pre-wrap; line-height: 1.4; }
118
- .field-row { display: flex; padding: 4px 0; }
119
- .field-label { color: ${dimText}; width: 100px; flex-shrink: 0; font-size: 11px; }
120
- .field-value { color: ${labelFg}; word-break: break-all; }
75
+ .log-level { width: 44px; font-weight: 600; font-size: 10px; text-transform: uppercase; flex-shrink: 0; }
76
+ .log-error { color: #e74c3c; }
77
+ .log-warn { color: #f39c12; }
78
+ .log-info { color: #888; }
79
+ .route-group { padding: 4px 0; }
80
+ .route-group-header { font-weight: 600; font-size: 11px; color: #2e8b57; padding: 3px 0; }
81
+ .route-item { padding: 1px 0 1px 14px; font-family: SF Mono,Menlo,monospace; font-size: 11px; }
82
+ .route-controller { color: #999; }
83
+ .exception-box { background: #fdf0ef; border-radius: 3px; padding: 8px; }
84
+ .exception-box .msg { color: #e74c3c; font-weight: 600; font-size: 12px; margin-bottom: 4px; }
85
+ .exception-box .stack { font-family: SF Mono,Menlo,monospace; font-size: 11px; color: #888; white-space: pre-wrap; line-height: 1.4; }
86
+ .field-row { display: flex; padding: 3px 0; font-size: 11px; }
87
+ .field-label { color: #888; width: 90px; flex-shrink: 0; }
88
+ .field-value { word-break: break-all; }
89
+ .error-text { color: #e74c3c; font-weight: 600; }
90
+ .warn-text { color: #f39c12; font-weight: 600; }
91
+ .ok-text { color: #27ae60; font-weight: 600; }
92
+ .status-badge { display: inline-block; padding: 1px 6px; border-radius: 2px; font-size: 11px; font-weight: 600; }
93
+ .status-200 { background: #d4edda; color: #155724; }
94
+ .status-300 { background: #fff3cd; color: #856404; }
95
+ .status-400 { background: #f8d7da; color: #721c24; }
96
+ .status-500 { background: #f8d7da; color: #721c24; }
121
97
  </style>
122
98
  </head>
123
99
  <body>
124
- <div class="header">
125
- <h1>NDev</h1>
126
- <span>NestJS Devtools Profiler</span>
127
- </div>
128
- <div class="container">
100
+ <div class="toolbar">
101
+ <span class="brand">ND</span>
102
+ <span class="title">NestJS Devtools Profiler</span>
129
103
  <div class="nav">
130
- <button class="active" data-tab="dashboard">Dashboard</button>
131
- <button data-tab="history">History</button>
104
+ <a class="active" data-tab="dashboard">Dashboard</a>
105
+ <a data-tab="history">History</a>
132
106
  </div>
133
- <div id="tab-dashboard" class="section active">
134
- <div class="grid" id="panel-grid"></div>
107
+ </div>
108
+ <div class="layout">
109
+ <div class="sidebar" id="sidebar">
110
+ <div class="section">
111
+ <h3>Request</h3>
112
+ <div id="sidebar-metrics"></div>
113
+ </div>
135
114
  </div>
136
- <div id="tab-history" class="section">
137
- <div class="history-wrap">
138
- <table class="history-table">
139
- <thead><tr><th>Time</th><th>Method</th><th>Path</th><th>Status</th><th>Duration</th></tr></thead>
140
- <tbody id="history-body"></tbody>
141
- </table>
115
+ <div class="main">
116
+ <div id="tab-dashboard" class="tab-content">
117
+ <div id="panel-list"></div>
118
+ </div>
119
+ <div id="tab-history" class="tab-content" style="display:none">
120
+ <div class="panel">
121
+ <table style="width:100%;border-collapse:collapse;font-size:12px">
122
+ <thead><tr style="background:#fafafa"><th style="padding:8px 12px;text-align:left;color:#888;font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid #ddd">Time</th><th style="padding:8px 12px;text-align:left;color:#888;font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid #ddd">Method</th><th style="padding:8px 12px;text-align:left;color:#888;font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid #ddd">Path</th><th style="padding:8px 12px;text-align:left;color:#888;font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid #ddd">Status</th><th style="padding:8px 12px;text-align:left;color:#888;font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid #ddd">Duration</th></tr></thead>
123
+ <tbody id="history-body"></tbody>
124
+ </table>
125
+ </div>
142
126
  </div>
143
127
  </div>
144
128
  </div>
145
129
  <script>
146
- (function() {
130
+ (function(){
147
131
  var BASE = window.location.pathname.replace(/\\/__devtools\\/?$/, '');
148
- var icons = ${JSON.stringify(I)};
132
+ var activeTab = 'dashboard';
133
+ var panelData = {};
149
134
 
150
135
  function escape(s) { return s==null?'':String(s).replace(/[&<>"]/g,function(m){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]}); }
151
136
 
152
- document.querySelectorAll('[data-tab]').forEach(function(btn){
153
- btn.addEventListener('click', function(){
137
+ document.querySelectorAll('[data-tab]').forEach(function(el){
138
+ el.addEventListener('click', function(){
154
139
  document.querySelectorAll('[data-tab]').forEach(function(b){b.classList.remove('active')});
155
- document.querySelectorAll('.section').forEach(function(s){s.classList.remove('active')});
156
- btn.classList.add('active');
157
- document.getElementById('tab-'+btn.getAttribute('data-tab')).classList.add('active');
158
- if (btn.getAttribute('data-tab')==='history') loadHistory();
140
+ document.querySelectorAll('.tab-content').forEach(function(c){c.style.display='none'});
141
+ el.classList.add('active');
142
+ activeTab = el.getAttribute('data-tab');
143
+ document.getElementById('tab-'+activeTab).style.display='';
144
+ if (activeTab==='history') loadHistory();
159
145
  });
160
146
  });
161
147
 
162
- function renderDetails(detail) {
148
+ function renderPanelContent(detail) {
163
149
  if (!detail || !detail.details) return '<div class="empty">No data</div>';
164
150
  var d = detail.details;
165
151
  var h = '<table>';
166
152
  for (var k in d) {
167
153
  var v = d[k];
168
- if (k === 'queries' && Array.isArray(v)) {
169
- if (v.length === 0) { h += '<tr><td colspan="2" style="padding:8px 0;color:${dimText}">No queries</td></tr>'; continue; }
170
- h += '<tr><th>Queries</th><td>';
154
+ if (k==='queries' && Array.isArray(v)) {
155
+ if (v.length===0) { h+='<tr><td colspan="2" style="padding:6px 0"><span class="empty">No queries</span></td></tr>'; continue; }
156
+ h+='<tr><th>Queries</th><td>';
171
157
  v.forEach(function(q,i){
172
- var cls = 'qlabel-'+((i%3)+1);
173
- h += '<div class="query-row"><span class="qlabel '+cls+'">'+(i+1)+'</span><span class="query-sql mono">'+escape(q.sql||'')+'</span><span class="query-time">'+escape(q.duration||'')+'</span></div>';
158
+ h+='<div class="query-row"><span class="query-num">'+(i+1)+'</span><span class="query-sql">'+escape(q.sql||'')+'</span><span class="query-time">'+escape(q.duration||'')+'</span></div>';
174
159
  });
175
- h += '</td></tr>';
160
+ h+='</td></tr>';
176
161
  continue;
177
162
  }
178
- if (k === 'entries' && Array.isArray(v)) {
179
- if (v.length === 0) { h += '<tr><td colspan="2" style="padding:8px 0;color:${dimText}">No entries</td></tr>'; continue; }
180
- h += '<tr><th>Logs</th><td>';
163
+ if (k==='entries' && Array.isArray(v)) {
164
+ if (v.length===0) { h+='<tr><td colspan="2" style="padding:6px 0"><span class="empty">No entries</span></td></tr>'; continue; }
165
+ h+='<tr><th>Logs</th><td>';
181
166
  v.forEach(function(e){
182
- var lc = e.level==='error' ? 'error-text' : e.level==='warn' ? 'warn-text' : '';
183
- h += '<div class="log-row"><span class="log-level" style="color:'+lc+'">'+escape(e.level||'')+'</span><span>'+escape(e.message||'')+'</span></div>';
167
+ var lc = e.level==='error' ? 'log-error' : e.level==='warn' ? 'log-warn' : 'log-info';
168
+ h+='<div class="log-row"><span class="log-level '+lc+'">'+escape(e.level||'')+'</span><span>'+escape(e.message||'')+'</span></div>';
184
169
  });
185
- h += '</td></tr>';
170
+ h+='</td></tr>';
186
171
  continue;
187
172
  }
188
- if (k === 'grouped' && Array.isArray(v)) {
189
- if (v.length === 0) { h += '<tr><td colspan="2" style="padding:8px 0;color:${dimText}">No routes</td></tr>'; continue; }
190
- h += '<tr><th>Routes</th><td>';
173
+ if (k==='grouped' && Array.isArray(v)) {
174
+ if (v.length===0) { h+='<tr><td colspan="2" style="padding:6px 0"><span class="empty">No routes</span></td></tr>'; continue; }
175
+ h+='<tr><th>Routes</th><td>';
191
176
  v.forEach(function(g){
192
- h += '<div class="route-group"><div class="route-group-header">'+escape(g.method||'')+' <span style="color:${dimText};font-weight:400">('+(g.count||0)+')</span></div>';
177
+ h+='<div class="route-group"><div class="route-group-header">'+escape(g.method||'')+' <span style="color:#999;font-weight:400">('+(g.count||0)+')</span></div>';
193
178
  if (g.routes) g.routes.forEach(function(r){
194
- h += '<div class="route-item">'+escape(r.path||'')+' <span class="route-controller">('+escape(r.controller||'')+')</span></div>';
179
+ h+='<div class="route-item">'+escape(r.path||'')+' <span class="route-controller">('+escape(r.controller||'')+')</span></div>';
195
180
  });
196
- h += '</div>';
181
+ h+='</div>';
197
182
  });
198
- h += '</td></tr>';
183
+ h+='</td></tr>';
199
184
  continue;
200
185
  }
201
- if (k === 'exception' && v && typeof v === 'object') {
202
- h += '<tr><td colspan="2"><div class="exception-box"><div class="msg">'+escape(v.message||'')+'</div><div class="stack">'+escape(v.stack||'')+'</div></div></td></tr>';
186
+ if (k==='exception' && v && typeof v==='object') {
187
+ h+='<tr><td colspan="2"><div class="exception-box"><div class="msg">'+escape(v.message||'')+'</div><div class="stack">'+escape(v.stack||'')+'</div></div></td></tr>';
203
188
  continue;
204
189
  }
205
- if (k === 'stack') continue;
206
- h += '<tr><th>'+escape(k)+'</th><td class="mono">'+escape(typeof v==='object'?JSON.stringify(v):v)+'</td></tr>';
190
+ if (k==='stack') continue;
191
+ h+='<tr><th>'+escape(k)+'</th><td class="mono">'+escape(typeof v==='object'?JSON.stringify(v):v)+'</td></tr>';
207
192
  }
208
- h += '</table>';
193
+ h+='</table>';
209
194
  return h;
210
195
  }
211
196
 
212
197
  function loadDashboard() {
213
198
  fetch(BASE + '/__devtools/data').then(function(r){return r.json()}).then(function(d){
214
- var grid = document.getElementById('panel-grid');
215
- grid.innerHTML = '';
216
- var iconMap = {request:icons.req,performance:icons.perf,database:icons.db,routes:icons.routes,logs:icons.log};
199
+ panelData = d;
200
+ var list = document.getElementById('panel-list');
201
+ var sidebar = document.getElementById('sidebar-metrics');
202
+ list.innerHTML = '';
203
+ sidebar.innerHTML = '';
217
204
  var order = ['request','performance','database','routes','logs'];
205
+ var firstReq = null;
218
206
  order.forEach(function(key){
219
207
  var p = d[key];
220
208
  if (!p || !p.label) return;
221
- var card = document.createElement('div');
222
- card.className = 'card';
223
- var icon = iconMap[key]||icons.log;
224
- var bodyHtml = renderDetails(p);
225
- card.innerHTML =
226
- '<div class="card-header"><span class="icon">'+icon+'</span><span class="label">'+escape(p.label)+'</span><span class="summary">'+escape(p.summary||'')+'</span></div>'+
227
- '<div class="card-body"><div class="card-body-inner">'+bodyHtml+'</div></div>';
228
- card.querySelector('.card-header').addEventListener('click', function(){
229
- var body = card.querySelector('.card-body');
209
+ if (!firstReq) firstReq = p;
210
+ var panel = document.createElement('div');
211
+ panel.className = 'panel';
212
+ panel.innerHTML =
213
+ '<div class="panel-header"><span>'+escape(p.label)+'</span><span class="summary">'+escape(p.summary||'')+'</span></div>'+
214
+ '<div class="panel-body">'+renderPanelContent(p)+'</div>';
215
+ panel.querySelector('.panel-header').addEventListener('click', function(){
216
+ var body = this.nextElementSibling;
230
217
  body.classList.toggle('open');
231
218
  });
232
- grid.appendChild(card);
219
+ list.appendChild(panel);
233
220
  });
234
221
  for (var key in d) {
235
- if (order.indexOf(key) !== -1) continue;
222
+ if (order.indexOf(key)!==-1) continue;
236
223
  var p = d[key];
237
224
  if (!p || !p.label) continue;
238
- var card = document.createElement('div');
239
- card.className = 'card';
240
- card.innerHTML =
241
- '<div class="card-header"><span class="icon">'+icons.log+'</span><span class="label">'+escape(p.label)+'</span><span class="summary">'+escape(p.summary||'')+'</span></div>'+
242
- '<div class="card-body"><div class="card-body-inner">'+renderDetails(p)+'</div></div>';
243
- card.querySelector('.card-header').addEventListener('click', function(){
244
- var body = card.querySelector('.card-body');
225
+ var panel = document.createElement('div');
226
+ panel.className = 'panel';
227
+ panel.innerHTML =
228
+ '<div class="panel-header"><span>'+escape(p.label)+'</span><span class="summary">'+escape(p.summary||'')+'</span></div>'+
229
+ '<div class="panel-body">'+renderPanelContent(p)+'</div>';
230
+ panel.querySelector('.panel-header').addEventListener('click', function(){
231
+ var body = this.nextElementSibling;
245
232
  body.classList.toggle('open');
246
233
  });
247
- grid.appendChild(card);
234
+ list.appendChild(panel);
235
+ }
236
+ if (firstReq && firstReq.details) {
237
+ var dd = firstReq.details;
238
+ sidebar.innerHTML =
239
+ '<div class="metric"><span class="label">Method</span><span class="value"><span class="badge badge-'+((dd.method||'get').toLowerCase())+'">'+escape(dd.method||'')+'</span></span></div>'+
240
+ '<div class="metric"><span class="label">Path</span><span class="value mono">'+escape(dd.path||'')+'</span></div>'+
241
+ '<div class="metric"><span class="label">Status</span><span class="value"><span class="status-badge status-'+(dd.status||'')+'">'+escape(dd.status||'')+'</span></span></div>'+
242
+ '<div class="metric"><span class="label">Duration</span><span class="value">'+escape(dd.duration||'')+'</span></div>'+
243
+ '<div class="metric"><span class="label">Memory</span><span class="value">'+escape(dd.memory||'')+'</span></div>'+
244
+ '<div class="metric"><span class="label">Route</span><span class="value mono" style="font-size:10px">'+escape(dd.route||'')+'</span></div>';
248
245
  }
249
- }).catch(function(){
250
- document.getElementById('panel-grid').innerHTML = '<div class="empty">Failed to load profiler data. Make sure the server is running.</div>';
246
+ }).catch(function(e){
247
+ console.error('[Devtools] Failed to load profiler data:', e);
248
+ document.getElementById('panel-list').innerHTML = '<div class="panel"><div class="panel-body open" style="display:block"><span class="empty">Failed to load profiler data.</span></div></div>';
251
249
  });
252
250
  }
253
251
 
@@ -255,23 +253,25 @@ const PROFILER_PAGE = (config) => {
255
253
  fetch(BASE + '/__devtools/history').then(function(r){return r.json()}).then(function(rows){
256
254
  var tbody = document.getElementById('history-body');
257
255
  tbody.innerHTML = '';
258
- if (!rows || rows.length === 0) {
259
- tbody.innerHTML = '<tr><td colspan="5" class="empty">No requests recorded yet</td></tr>';
256
+ if (!rows || rows.length===0) {
257
+ tbody.innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#bbb">No requests recorded yet</td></tr>';
260
258
  return;
261
259
  }
262
260
  rows.forEach(function(r){
263
- var statusClass = r.statusCode >= 400 ? 'error-text' : r.statusCode >= 300 ? 'warn-text' : 'ok-text';
264
- var methodBadge = 'badge-'+r.method.toLowerCase();
261
+ var sc = r.statusCode;
262
+ var scCls = sc>=400 ? 'error-text' : sc>=300 ? 'warn-text' : 'ok-text';
263
+ var mCls = 'badge-'+r.method.toLowerCase();
265
264
  tbody.innerHTML += '<tr>'+
266
- '<td style="color:${dimText}">'+escape(new Date(r.timestamp).toLocaleTimeString())+'</td>'+
267
- '<td><span class="badge '+methodBadge+'">'+escape(r.method)+'</span></td>'+
268
- '<td class="mono">'+escape(r.path)+'</td>'+
269
- '<td><span class="'+statusClass+'">'+r.statusCode+'</span></td>'+
270
- '<td class="mono" style="color:${dimText}">'+(r.duration!=null?r.duration.toFixed(1):'')+' ms</td>'+
265
+ '<td style="padding:8px 12px;border-bottom:1px solid #eee;color:#888">'+escape(new Date(r.timestamp).toLocaleTimeString())+'</td>'+
266
+ '<td style="padding:8px 12px;border-bottom:1px solid #eee"><span class="badge '+mCls+'">'+escape(r.method)+'</span></td>'+
267
+ '<td style="padding:8px 12px;border-bottom:1px solid #eee;font-family:SF Mono,Menlo,monospace;font-size:11px">'+escape(r.path)+'</td>'+
268
+ '<td style="padding:8px 12px;border-bottom:1px solid #eee"><span class="'+scCls+'">'+sc+'</span></td>'+
269
+ '<td style="padding:8px 12px;border-bottom:1px solid #eee;color:#888;font-family:SF Mono,Menlo,monospace">'+(r.duration!=null?r.duration.toFixed(1):'')+' ms</td>'+
271
270
  '</tr>';
272
271
  });
273
- }).catch(function(){
274
- document.getElementById('history-body').innerHTML = '<tr><td colspan="5" class="empty">Failed to load history</td></tr>';
272
+ }).catch(function(e){
273
+ console.error('[Devtools] Failed to load history:', e);
274
+ document.getElementById('history-body').innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#bbb">Failed to load history</td></tr>';
275
275
  });
276
276
  }
277
277
 
@@ -1 +1 @@
1
- {"version":3,"file":"profiler.js","sourceRoot":"","sources":["../../toolbar/profiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"profiler.js","sourceRoot":"","sources":["../../toolbar/profiler.ts"],"names":[],"mappings":";;;AAIO,MAAM,aAAa,GAAG,CAAC,MAAoC,EAAE,EAAE;IACpE,MAAM,EAAE,KAAK,GAAG,MAAM,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;IAExC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoRD,CAAC;AACT,CAAC,CAAC;AAxRW,QAAA,aAAa,iBAwRxB"}
@@ -1,50 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TOOLBAR_TEMPLATE = void 0;
4
- const I = {
5
- req: '<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M5 8h6M8 5l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
6
- perf: '<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M8 4v4l2.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
7
- db: '<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><ellipse cx="8" cy="4.5" rx="5" ry="2" stroke="currentColor" stroke-width="1.5"/><path d="M3 4.5v7c0 1.1 2.2 2 5 2s5-.9 5-2v-7" stroke="currentColor" stroke-width="1.5"/><path d="M3 8c0 1.1 2.2 2 5 2s5-.9 5-2" stroke="currentColor" stroke-width="1.5"/></svg>',
8
- routes: '<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
9
- log: '<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="12" height="12" rx="2" stroke="currentColor" stroke-width="1.5"/><path d="M5 6h6M5 9h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
10
- close: '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
11
- profiler: '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M2 3h12M2 8h12M2 13h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="12" cy="12" r="2.5" stroke="currentColor" stroke-width="1.5"/></svg>',
12
- chevron: '<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
13
- };
14
4
  const TOOLBAR_TEMPLATE = (config) => {
15
5
  const { position = 'bottom-left', theme = 'dark' } = config ?? {};
16
6
  const id = '__nestDevtools';
17
7
  const uid = Math.random().toString(36).slice(2, 8);
18
8
  const isTop = position.startsWith('top');
19
- const isRight = position.endsWith('right');
20
9
  const outerPos = isTop ? 'top:0' : 'bottom:0';
21
- const outerSide = isRight ? 'right:0' : 'left:0';
22
- const outerBorder = isTop ? 'border-bottom:1px solid' : 'border-top:1px solid';
23
10
  const outerShadow = isTop
24
- ? 'box-shadow:0 4px 24px rgba(0,0,0,0.3)'
25
- : 'box-shadow:0 -4px 24px rgba(0,0,0,0.3)';
26
- const dark = theme === 'dark';
27
- const bg = dark ? '#0f172a' : '#fafbfc';
28
- const fg = dark ? '#e2e8f0' : '#1e293b';
29
- const accent = dark ? '#38bdf8' : '#0284c7';
30
- const accentBg = dark ? '#0c4a6e30' : '#e0f2fe';
31
- const panelBg = dark ? '#1e293b' : '#ffffff';
32
- const borderClr = dark ? '#1e293b' : '#e2e8f0';
33
- const panelBorder = dark ? '#334155' : '#e2e8f0';
34
- const btnFg = dark ? '#94a3b8' : '#475569';
35
- const btnBg = dark ? '#1e293b' : '#f1f5f9';
36
- const btnHover = dark ? '#334155' : '#e2e8f0';
37
- const dimText = dark ? '#64748b' : '#94a3b8';
38
- const labelFg = dark ? '#f1f5f9' : '#0f172a';
39
- const warnClr = '#f59e0b';
40
- const errClr = '#ef4444';
41
- const sqlTimeClr = dark ? '#fbbf24' : '#b45309';
11
+ ? 'box-shadow:0 2px 14px rgba(0,0,0,0.5)'
12
+ : 'box-shadow:0 -2px 14px rgba(0,0,0,0.5)';
42
13
  return `<script id="${id}">
43
14
  (function(){
44
15
  var D=document, B=D.body, C='__nestDt'+'${uid}';
45
16
  var p = window.location.pathname.replace(\/[^\/]*$/,'') + '/__devtools';
46
17
  var c=D.createElement('div'); c.id=C;
47
- c.innerHTML='<div style="position:fixed;${outerPos};${outerSide};z-index:99999;font-family:Inter,SF Pro,system-ui,-apple-system,sans-serif;font-size:12px;background:${bg};color:${fg};${outerBorder}:${borderClr};width:100%;${outerShadow}"><div style="display:flex;align-items:center;height:36px;padding:0 4px 0 10px;gap:2px;overflow-x:auto;white-space:nowrap;scrollbar-width:none"><span style="font-weight:700;font-size:13px;color:${accent};letter-spacing:-0.3px;padding-right:8px;flex-shrink:0">ND</span><div id="'+C+'-bar" style="display:flex;align-items:center;gap:2px;flex:1"></div><a href="'+p+'" target="_blank" style="display:inline-flex;align-items:center;gap:4px;color:${dimText};text-decoration:none;padding:4px 8px;font-size:11px;flex-shrink:0" title="Open profiler">${I.profiler}<span>Profiler</span></a><span id="'+C+'-toggle" style="display:inline-flex;align-items:center;color:${dimText};cursor:pointer;padding:4px 6px;font-size:10px;flex-shrink:0">${I.chevron}</span></div><div id="'+C+'-panel" style="display:none;border-top:1px solid ${panelBorder};background:${panelBg};font-size:12px;line-height:1.5;max-height:400px;overflow:hidden"></div></div>';
18
+ c.innerHTML='<div style="position:fixed;${outerPos};left:0;right:0;z-index:99999;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;background:#1a1a1a;color:#ccc;${outerShadow}"><div style="display:flex;align-items:center;height:26px;padding:0 6px;overflow-x:auto;white-space:nowrap;scrollbar-width:none"><span style="font-weight:700;font-size:12px;color:#888;padding:0 10px 0 4px;letter-spacing:0;flex-shrink:0">ND</span><div id="'+C+'-bar" style="display:flex;align-items:center;flex:1;height:100%"></div><a href="'+p+'" target="_blank" style="display:inline-flex;align-items:center;gap:4px;color:#666;text-decoration:none;padding:0 8px;font-size:11px;height:100%;flex-shrink:0" title="Open profiler"><svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></a><span id="'+C+'-toggle" style="display:inline-flex;align-items:center;justify-content:center;color:#666;cursor:pointer;padding:0 6px;font-size:13px;height:100%;flex-shrink:0">\u2039</span></div><div id="'+C+'-panel" style="display:none;background:#2b2b2b;font-size:12px;line-height:1.5;color:#ccc;max-height:420px;overflow:hidden"></div></div>';
48
19
  B.appendChild(c);
49
20
 
50
21
  var lastActive=null;
@@ -52,57 +23,62 @@ const TOOLBAR_TEMPLATE = (config) => {
52
23
  var pnl=D.getElementById(C+'-panel');
53
24
  if (lastActive===name) { pnl.style.display='none'; lastActive=null; return; }
54
25
  lastActive=name;
55
- var h='<div style="display:flex;align-items:center;gap:8px;padding:10px 14px;border-bottom:1px solid ${panelBorder}">';
56
- h+='<span style="font-weight:600;font-size:13px;color:${labelFg}">'+escapeHtml(detail.label)+'</span>';
57
- h+='<span style="color:${dimText};font-size:11px">'+escapeHtml(detail.summary)+'</span>';
58
- h+='<span style="margin-left:auto;cursor:pointer;color:${dimText};display:inline-flex;padding:2px" onclick="document.getElementById('+C+'-panel).style.display=\\'none\\';lastActive=null">${I.close}</span>';
26
+ var h='<div style="display:flex;align-items:center;gap:10px;padding:9px 14px;border-bottom:1px solid #333">';
27
+ h+='<span style="font-weight:600;font-size:13px;color:#eee">'+escapeHtml(detail.label)+'</span>';
28
+ h+='<span style="color:#888;font-size:11px">'+escapeHtml(detail.summary)+'</span>';
29
+ h+='<span style="margin-left:auto;cursor:pointer;color:#666;padding:2px;font-size:15px" onclick="document.getElementById('+C+'-panel).style.display=\\'none\\';lastActive=null">\u00d7</span>';
59
30
  h+='</div>';
60
- h+='<div style="padding:8px 14px 14px;overflow-y:auto;max-height:340px">';
61
- h+='<table style="width:100%;border-collapse:collapse;font-size:12px">';
31
+ h+='<div style="padding:10px 14px 14px;overflow-y:auto;max-height:360px">';
32
+ var fields=[];
62
33
  for (var k in detail.details) {
63
34
  var v=detail.details[k];
64
- if (k==='queries' && Array.isArray(v)) {
65
- if (v.length===0) { h+='<tr><td style="padding:8px;color:${dimText}">No queries recorded</td></tr>'; continue; }
66
- for (var i=0;i<v.length;i++) {
67
- h+='<tr><td style="padding:5px 8px;color:${dimText};vertical-align:top;width:28px;font-size:11px;font-weight:500">'+(i+1)+'</td><td style="padding:5px 8px;font-family:ui-monospace,SF Mono,Menlo,monospace;font-size:11px;word-break:break-all;color:${labelFg}">'+escapeHtml(v[i].sql||'')+'</td><td style="padding:5px 8px;text-align:right;color:${sqlTimeClr};white-space:nowrap;font-family:ui-monospace,SF Mono,monospace;font-size:11px">'+escapeHtml(v[i].duration||'')+'</td></tr>';
35
+ if (k==='queries' && Array.isArray(v)) { fields.push({type:'queries',data:v}); continue; }
36
+ if (k==='entries' && Array.isArray(v)) { fields.push({type:'log',data:v}); continue; }
37
+ if (k==='grouped' && Array.isArray(v)) { fields.push({type:'routes',data:v}); continue; }
38
+ if (k==='exception' && v && typeof v==='object') { fields.push({type:'exception',data:v}); continue; }
39
+ if (k==='stack') continue;
40
+ fields.push({type:'pair',key:k,val:v});
41
+ }
42
+ for (var fi=0;fi<fields.length;fi++) {
43
+ var f=fields[fi];
44
+ if (f.type==='queries') {
45
+ if (f.data.length===0) { h+='<div style="padding:6px 0;color:#666">No queries</div>'; continue; }
46
+ for (var qi=0;qi<f.data.length;qi++) {
47
+ h+='<div style="display:flex;align-items:flex-start;gap:6px;padding:5px 0;border-bottom:1px solid #333;font-size:11px"><span style="color:#666;width:22px;flex-shrink:0">'+(qi+1)+'</span><span style="font-family:SF Mono,Menlo,monospace;word-break:break-all;flex:1">'+escapeHtml(f.data[qi].sql||'')+'</span><span style="color:#dfb840;white-space:nowrap;flex-shrink:0;font-family:SF Mono,Menlo,monospace">'+escapeHtml(f.data[qi].duration||'')+'</span></div>';
68
48
  }
69
49
  continue;
70
50
  }
71
- if (k==='entries' && Array.isArray(v)) {
72
- if (v.length===0) { h+='<tr><td style="padding:8px;color:${dimText}">No entries</td></tr>'; continue; }
73
- for (var i=0;i<v.length;i++) {
74
- var e=v[i];
75
- var lc=e.level==='error'?'${errClr}':e.level==='warn'?'${warnClr}':'${dimText}';
76
- var bgc=e.level==='error'?'rgba(239,68,68,0.08)':e.level==='warn'?'rgba(245,158,11,0.08)':'transparent';
77
- h+='<tr style="background:'+bgc+'"><td style="padding:5px 8px;color:'+lc+';width:52px;font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:0.3px">'+escapeHtml(e.level||'')+'</td><td style="padding:5px 8px;word-break:break-all;color:${labelFg}">'+escapeHtml(e.message||'')+'</td></tr>';
51
+ if (f.type==='log') {
52
+ if (f.data.length===0) { h+='<div style="padding:6px 0;color:#666">No entries</div>'; continue; }
53
+ for (var li=0;li<f.data.length;li++) {
54
+ var e=f.data[li];
55
+ var lc=e.level==='error'?'#e74c3c':e.level==='warn'?'#f39c12':'#888';
56
+ h+='<div style="display:flex;align-items:flex-start;gap:8px;padding:4px 0;border-bottom:1px solid #333;font-size:11px"><span style="color:'+lc+';width:44px;font-weight:600;font-size:10px;text-transform:uppercase;flex-shrink:0">'+escapeHtml(e.level||'')+'</span><span>'+escapeHtml(e.message||'')+'</span></div>';
78
57
  }
79
58
  continue;
80
59
  }
81
- if (k==='grouped' && Array.isArray(v)) {
82
- for (var i=0;i<v.length;i++) {
83
- var g=v[i];
84
- h+='<tr><td colspan="2" style="padding:7px 8px 3px;color:${accent};font-weight:600;font-size:11px">'+escapeHtml(g.method||'')+' <span style="color:${dimText};font-weight:400">('+(g.count||0)+')</span></td></tr>';
60
+ if (f.type==='routes') {
61
+ for (var gi=0;gi<f.data.length;gi++) {
62
+ var g=f.data[gi];
63
+ h+='<div style="padding:5px 0 2px;font-weight:600;font-size:11px;color:#5d9">'+escapeHtml(g.method||'')+' <span style="color:#666;font-weight:400">('+(g.count||0)+')</span></div>';
85
64
  if (g.routes) {
86
- for (var j=0;j<g.routes.length;j++) {
87
- h+='<tr><td style="padding:2px 8px 2px 16px;font-family:ui-monospace,SF Mono,monospace;font-size:11px;color:${labelFg}" colspan="2">'+escapeHtml(g.routes[j].path||'')+' <span style="color:${dimText}">('+escapeHtml(g.routes[j].controller||'')+')</span></td></tr>';
65
+ for (var rj=0;rj<g.routes.length;rj++) {
66
+ h+='<div style="padding:2px 0 2px 14px;font-family:SF Mono,Menlo,monospace;font-size:11px">'+escapeHtml(g.routes[rj].path||'')+' <span style="color:#666">('+escapeHtml(g.routes[rj].controller||'')+')</span></div>';
88
67
  }
89
68
  }
90
69
  }
91
70
  continue;
92
71
  }
93
- if (k==='exception' && v && typeof v==='object') {
94
- h+='<tr><td style="padding:5px 8px;color:${errClr};font-weight:600" colspan="2">'+escapeHtml(v.message||'')+'</td></tr>';
95
- h+='<tr><td colspan="2" style="padding:5px 8px"><pre style="font-family:ui-monospace,SF Mono,monospace;font-size:11px;color:${dimText};white-space:pre-wrap;margin:0;line-height:1.4">'+escapeHtml(v.stack||'')+'</pre></td></tr>';
72
+ if (f.type==='exception') {
73
+ h+='<div style="padding:8px;background:rgba(231,76,60,0.1);border-radius:4px"><div style="color:#e74c3c;font-weight:600;font-size:12px;margin-bottom:4px">'+escapeHtml(f.data.message||'')+'</div><pre style="font-family:SF Mono,Menlo,monospace;font-size:11px;color:#888;white-space:pre-wrap;margin:0;line-height:1.4">'+escapeHtml(f.data.stack||'')+'</pre></div>';
96
74
  continue;
97
75
  }
98
- if (k==='stack') continue;
99
- if (typeof v==='object' && v!==null) continue;
100
- h+='<tr><td style="padding:4px 8px;color:${dimText};width:90px;vertical-align:top;font-size:11px">'+escapeHtml(k)+'</td><td style="padding:4px 8px;word-break:break-all;color:${labelFg}">'+escapeHtml(String(v))+'</td></tr>';
76
+ h+='<div style="display:flex;padding:3px 0;font-size:11px"><span style="color:#888;width:90px;flex-shrink:0">'+escapeHtml(f.key)+'</span><span style="word-break:break-all">'+escapeHtml(typeof f.val==='object'?JSON.stringify(f.val):String(f.val))+'</span></div>';
101
77
  }
102
- h+='</table></div>';
78
+ h+='</div>';
103
79
  pnl.innerHTML=h;
104
80
  pnl.style.display='block';
105
- D.getElementById(C+'-toggle').innerHTML='${I.chevron}';
81
+ D.getElementById(C+'-toggle').innerHTML='\u203a';
106
82
  }
107
83
 
108
84
  function escapeHtml(s) {
@@ -110,40 +86,37 @@ const TOOLBAR_TEMPLATE = (config) => {
110
86
  return String(s).replace(/[&<>"]/g,function(m){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]});
111
87
  }
112
88
 
113
- var icons = {
114
- request: '${I.req}',
115
- performance: '${I.perf}',
116
- database: '${I.db}',
117
- routes: '${I.routes}',
118
- logs: '${I.log}',
119
- };
89
+ function makeBadge(html, cls) {
90
+ var el=D.createElement('span');
91
+ el.style.cssText='display:inline-flex;align-items:center;height:100%;padding:0 8px;color:#ccc;cursor:pointer;gap:4px;font-size:11px;border-right:1px solid #333;transition:background .1s';
92
+ el.innerHTML=html;
93
+ el.onmouseenter=function(){this.style.background='#333'};
94
+ el.onmouseleave=function(){this.style.background='transparent'};
95
+ if (cls) el.className=cls;
96
+ return el;
97
+ }
120
98
 
121
99
  var furl = p + '/data';
122
100
  fetch(furl).then(function(r){return r.json()}).then(function(d){
123
101
  var bar=D.getElementById(C+'-bar');
124
102
  var order=['request','performance','database','routes','logs'];
125
- var panels=[];
126
- for(var oi=0;oi<order.length;oi++){if(d[order[oi]])panels.push({key:order[oi],data:d[order[oi]]});}
127
- for(var k in d){var found=false;for(var pi=0;pi<panels.length;pi++){if(panels[pi].key===k){found=true;break}}if(!found&&d[k]&&d[k].label)panels.push({key:k,data:d[k]});}
128
- panels.forEach(function(item,idx){
103
+ var items=[];
104
+ for(var oi=0;oi<order.length;oi++){if(d[order[oi]])items.push({key:order[oi],data:d[order[oi]]});}
105
+ for(var k in d){var found=false;for(var pi=0;pi<items.length;pi++){if(items[pi].key===k){found=true;break}}if(!found&&d[k]&&d[k].label)items.push({key:k,data:d[k]});}
106
+ items.forEach(function(item){
129
107
  var p=item.data;
130
- var ic=icons[item.key]||'${I.log}';
131
- var btn=D.createElement('span');
132
- btn.style.cssText='display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:6px;cursor:pointer;color:${btnFg};background:${btnBg};margin:0 1px;transition:all .12s ease;font-size:11px;font-weight:500';
133
- btn.innerHTML=ic+'<span>'+escapeHtml(p.summary)+'</span>';
134
- btn.onmouseenter=function(){this.style.background='${btnHover}';this.style.color='${fg}'};
135
- btn.onmouseleave=function(){this.style.background='${btnBg}';this.style.color='${btnFg}'};
136
- btn.onclick=function(n){return function(){
137
- var key=Object.keys(d).filter(function(kr){return d[kr]===d[n]})[0];
108
+ var badge=makeBadge('<span style="color:#888;font-weight:500">'+escapeHtml(p.summary)+'</span>');
109
+ badge.onclick=function(){
110
+ var key=Object.keys(d).filter(function(kr){return d[kr]===d[p]})[0];
138
111
  renderPanel(key,d[key]);
139
- }}(item.key);
140
- bar.appendChild(btn);
112
+ };
113
+ bar.appendChild(badge);
141
114
  });
142
115
  D.getElementById(C+'-toggle').onclick=function(){
143
116
  var pnl=D.getElementById(C+'-panel');
144
117
  var isOpen=pnl.style.display==='block';
145
118
  pnl.style.display=isOpen?'none':'block';
146
- this.innerHTML=isOpen?'${I.chevron}':'${I.close}';
119
+ this.innerHTML=isOpen?'\u2039':'\u203a';
147
120
  if(isOpen)lastActive=null;
148
121
  };
149
122
  }).catch(function(){});
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../toolbar/template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../toolbar/template.ts"],"names":[],"mappings":";;;AAKO,MAAM,gBAAgB,GAAG,CAAC,MAAuC,EAAE,EAAE;IAC1E,MAAM,EAAE,QAAQ,GAAG,aAAa,EAAE,KAAK,GAAG,MAAM,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;IAClE,MAAM,EAAE,GAAG,gBAAgB,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IAC9C,MAAM,WAAW,GAAG,KAAK;QACvB,CAAC,CAAC,uCAAuC;QACzC,CAAC,CAAC,wCAAwC,CAAC;IAE7C,OAAO,eAAe,EAAE;;4CAEkB,GAAG;;;4CAGH,QAAQ,sIAAsI,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA0G3L,CAAC;AACX,CAAC,CAAC;AA1HW,QAAA,gBAAgB,oBA0H3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-devtools",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Symfony-style debug toolbar for NestJS applications",
5
5
  "keywords": [
6
6
  "nestjs",