nestjs-devtools 1.3.2 → 1.5.0
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/toolbar/profiler.js +190 -147
- package/dist/toolbar/profiler.js.map +1 -1
- package/dist/toolbar/template.js +71 -69
- package/dist/toolbar/template.js.map +1 -1
- package/package.json +1 -1
package/dist/toolbar/profiler.js
CHANGED
|
@@ -3,17 +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 ? '#111827' : '#f8fafc';
|
|
8
|
-
const fg = dark ? '#f3f4f6' : '#1f2937';
|
|
9
|
-
const cardBg = dark ? '#1f2937' : '#ffffff';
|
|
10
|
-
const borderClr = dark ? '#374151' : '#e5e7eb';
|
|
11
|
-
const headerBg = dark ? '#1f2937' : '#ffffff';
|
|
12
|
-
const dimText = dark ? '#9ca3af' : '#6b7280';
|
|
13
|
-
const dimmerText = dark ? '#6b7280' : '#9ca3af';
|
|
14
|
-
const accentText = dark ? '#60a5fa' : '#2563eb';
|
|
15
|
-
const navBg = dark ? '#1f2937' : '#f1f5f9';
|
|
16
|
-
const navFg = dark ? '#d1d5db' : '#475569';
|
|
17
6
|
return `<!DOCTYPE html>
|
|
18
7
|
<html lang="en">
|
|
19
8
|
<head>
|
|
@@ -22,187 +11,240 @@ const PROFILER_PAGE = (config) => {
|
|
|
22
11
|
<title>NestJS Devtools Profiler</title>
|
|
23
12
|
<style>
|
|
24
13
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
14
|
+
html, body { height: 100%; }
|
|
25
15
|
body {
|
|
26
|
-
font-family: -apple-system,
|
|
27
|
-
background:
|
|
28
|
-
min-height: 100vh;
|
|
16
|
+
font-family: -apple-system,BlinkMacSystemFont,sans-serif;
|
|
17
|
+
background: #f5f5f5; color: #222; font-size: 13px; line-height: 1.5;
|
|
29
18
|
}
|
|
30
|
-
.
|
|
31
|
-
background:
|
|
32
|
-
|
|
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;
|
|
33
23
|
}
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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;
|
|
41
31
|
}
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
46
38
|
}
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
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;
|
|
56
56
|
}
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
.
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
.
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
.
|
|
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; }
|
|
69
|
+
.query-row:last-child { border-bottom: none; }
|
|
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; }
|
|
74
|
+
.log-row:last-child { border-bottom: none; }
|
|
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; }
|
|
89
97
|
</style>
|
|
90
98
|
</head>
|
|
91
99
|
<body>
|
|
92
|
-
<div class="
|
|
93
|
-
<
|
|
94
|
-
<span>NestJS Devtools Profiler</span>
|
|
95
|
-
</div>
|
|
96
|
-
<div class="container">
|
|
100
|
+
<div class="toolbar">
|
|
101
|
+
<span class="brand">ND</span>
|
|
102
|
+
<span class="title">NestJS Devtools Profiler</span>
|
|
97
103
|
<div class="nav">
|
|
98
|
-
<
|
|
99
|
-
<
|
|
104
|
+
<a class="active" data-tab="dashboard">Dashboard</a>
|
|
105
|
+
<a data-tab="history">History</a>
|
|
100
106
|
</div>
|
|
101
|
-
|
|
102
|
-
|
|
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>
|
|
103
114
|
</div>
|
|
104
|
-
<div
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
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>
|
|
126
|
+
</div>
|
|
109
127
|
</div>
|
|
110
128
|
</div>
|
|
111
129
|
<script>
|
|
112
|
-
(function()
|
|
130
|
+
(function(){
|
|
113
131
|
var BASE = window.location.pathname.replace(/\\/__devtools\\/?$/, '');
|
|
132
|
+
var activeTab = 'dashboard';
|
|
133
|
+
var panelData = {};
|
|
134
|
+
|
|
114
135
|
function escape(s) { return s==null?'':String(s).replace(/[&<>"]/g,function(m){return {'&':'&','<':'<','>':'>','"':'"'}[m]}); }
|
|
115
136
|
|
|
116
|
-
document.querySelectorAll('[data-tab]').forEach(function(
|
|
117
|
-
|
|
137
|
+
document.querySelectorAll('[data-tab]').forEach(function(el){
|
|
138
|
+
el.addEventListener('click', function(){
|
|
118
139
|
document.querySelectorAll('[data-tab]').forEach(function(b){b.classList.remove('active')});
|
|
119
|
-
document.querySelectorAll('.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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();
|
|
123
145
|
});
|
|
124
146
|
});
|
|
125
147
|
|
|
126
|
-
function
|
|
148
|
+
function renderPanelContent(detail) {
|
|
127
149
|
if (!detail || !detail.details) return '<div class="empty">No data</div>';
|
|
128
150
|
var d = detail.details;
|
|
129
151
|
var h = '<table>';
|
|
130
152
|
for (var k in d) {
|
|
131
153
|
var v = d[k];
|
|
132
|
-
if (k
|
|
133
|
-
h
|
|
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>';
|
|
134
157
|
v.forEach(function(q,i){
|
|
135
|
-
|
|
136
|
-
h += '<div style="padding:4px 0;border-bottom:1px solid #1f2937">';
|
|
137
|
-
h += '<span class="qlabel '+cls+'">#'+(i+1)+'</span>';
|
|
138
|
-
h += '<span class="mono">'+escape(q.sql||'')+'</span>';
|
|
139
|
-
h += '<span style="float:right;color:#fbbf24">'+escape(q.duration||'')+'</span>';
|
|
140
|
-
h += '</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>';
|
|
141
159
|
});
|
|
142
|
-
h
|
|
160
|
+
h+='</td></tr>';
|
|
143
161
|
continue;
|
|
144
162
|
}
|
|
145
|
-
if (k
|
|
146
|
-
h
|
|
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>';
|
|
147
166
|
v.forEach(function(e){
|
|
148
|
-
var lc = e.level==='error' ? 'error
|
|
149
|
-
h
|
|
150
|
-
h += '<span class="badge '+lc+'" style="background:#1f2937;text-transform:uppercase;font-size:10px;margin-right:6px">'+escape(e.level||'')+'</span>';
|
|
151
|
-
h += '<span>'+escape(e.message||'')+'</span>';
|
|
152
|
-
h += '</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>';
|
|
153
169
|
});
|
|
154
|
-
h
|
|
170
|
+
h+='</td></tr>';
|
|
155
171
|
continue;
|
|
156
172
|
}
|
|
157
|
-
if (k
|
|
158
|
-
h
|
|
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>';
|
|
159
176
|
v.forEach(function(g){
|
|
160
|
-
h
|
|
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>';
|
|
161
178
|
if (g.routes) g.routes.forEach(function(r){
|
|
162
|
-
h
|
|
163
|
-
h += escape(r.path||'')+' <span style="color:#6b7280">('+escape(r.controller||'')+')</span></div>';
|
|
179
|
+
h+='<div class="route-item">'+escape(r.path||'')+' <span class="route-controller">('+escape(r.controller||'')+')</span></div>';
|
|
164
180
|
});
|
|
181
|
+
h+='</div>';
|
|
165
182
|
});
|
|
166
|
-
h
|
|
183
|
+
h+='</td></tr>';
|
|
167
184
|
continue;
|
|
168
185
|
}
|
|
169
|
-
if (k
|
|
170
|
-
h
|
|
171
|
-
h += '<div class="error-text" style="font-weight:600">'+escape(v.message||'')+'</div>';
|
|
172
|
-
h += '<pre class="mono" style="margin-top:6px;color:#9ca3af;white-space:pre-wrap">'+escape(v.stack||'')+'</pre>';
|
|
173
|
-
h += '</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>';
|
|
174
188
|
continue;
|
|
175
189
|
}
|
|
176
|
-
if (k
|
|
177
|
-
h
|
|
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>';
|
|
178
192
|
}
|
|
179
|
-
h
|
|
193
|
+
h+='</table>';
|
|
180
194
|
return h;
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
function loadDashboard() {
|
|
184
198
|
fetch(BASE + '/__devtools/data').then(function(r){return r.json()}).then(function(d){
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
var
|
|
188
|
-
|
|
199
|
+
panelData = d;
|
|
200
|
+
var list = document.getElementById('panel-list');
|
|
201
|
+
var sidebar = document.getElementById('sidebar-metrics');
|
|
202
|
+
list.innerHTML = '';
|
|
203
|
+
sidebar.innerHTML = '';
|
|
204
|
+
var order = ['request','performance','database','routes','logs'];
|
|
205
|
+
var firstReq = null;
|
|
206
|
+
order.forEach(function(key){
|
|
189
207
|
var p = d[key];
|
|
190
208
|
if (!p || !p.label) return;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
'<
|
|
196
|
-
'<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
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;
|
|
200
217
|
body.classList.toggle('open');
|
|
201
218
|
});
|
|
202
|
-
|
|
219
|
+
list.appendChild(panel);
|
|
203
220
|
});
|
|
221
|
+
for (var key in d) {
|
|
222
|
+
if (order.indexOf(key)!==-1) continue;
|
|
223
|
+
var p = d[key];
|
|
224
|
+
if (!p || !p.label) continue;
|
|
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;
|
|
232
|
+
body.classList.toggle('open');
|
|
233
|
+
});
|
|
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>';
|
|
245
|
+
}
|
|
204
246
|
}).catch(function(){
|
|
205
|
-
document.getElementById('panel-
|
|
247
|
+
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>';
|
|
206
248
|
});
|
|
207
249
|
}
|
|
208
250
|
|
|
@@ -210,23 +252,24 @@ const PROFILER_PAGE = (config) => {
|
|
|
210
252
|
fetch(BASE + '/__devtools/history').then(function(r){return r.json()}).then(function(rows){
|
|
211
253
|
var tbody = document.getElementById('history-body');
|
|
212
254
|
tbody.innerHTML = '';
|
|
213
|
-
if (!rows || rows.length
|
|
214
|
-
tbody.innerHTML = '<tr><td colspan="5"
|
|
255
|
+
if (!rows || rows.length===0) {
|
|
256
|
+
tbody.innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#bbb">No requests recorded yet</td></tr>';
|
|
215
257
|
return;
|
|
216
258
|
}
|
|
217
259
|
rows.forEach(function(r){
|
|
218
|
-
var
|
|
219
|
-
var
|
|
260
|
+
var sc = r.statusCode;
|
|
261
|
+
var scCls = sc>=400 ? 'error-text' : sc>=300 ? 'warn-text' : 'ok-text';
|
|
262
|
+
var mCls = 'badge-'+r.method.toLowerCase();
|
|
220
263
|
tbody.innerHTML += '<tr>'+
|
|
221
|
-
'<td>'+escape(new Date(r.timestamp).toLocaleTimeString())+'</td>'+
|
|
222
|
-
'<td><span class="badge '+
|
|
223
|
-
'<td
|
|
224
|
-
'<td><span class="'+
|
|
225
|
-
'<td>'+r.duration.toFixed(1)+' ms</td>'+
|
|
264
|
+
'<td style="padding:8px 12px;border-bottom:1px solid #eee;color:#888">'+escape(new Date(r.timestamp).toLocaleTimeString())+'</td>'+
|
|
265
|
+
'<td style="padding:8px 12px;border-bottom:1px solid #eee"><span class="badge '+mCls+'">'+escape(r.method)+'</span></td>'+
|
|
266
|
+
'<td style="padding:8px 12px;border-bottom:1px solid #eee;font-family:SF Mono,Menlo,monospace;font-size:11px">'+escape(r.path)+'</td>'+
|
|
267
|
+
'<td style="padding:8px 12px;border-bottom:1px solid #eee"><span class="'+scCls+'">'+sc+'</span></td>'+
|
|
268
|
+
'<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>'+
|
|
226
269
|
'</tr>';
|
|
227
270
|
});
|
|
228
271
|
}).catch(function(){
|
|
229
|
-
document.getElementById('history-body').innerHTML = '<tr><td colspan="5"
|
|
272
|
+
document.getElementById('history-body').innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#bbb">Failed to load history</td></tr>';
|
|
230
273
|
});
|
|
231
274
|
}
|
|
232
275
|
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAkRD,CAAC;AACT,CAAC,CAAC;AAtRW,QAAA,aAAa,iBAsRxB"}
|
package/dist/toolbar/template.js
CHANGED
|
@@ -6,116 +6,118 @@ const TOOLBAR_TEMPLATE = (config) => {
|
|
|
6
6
|
const id = '__nestDevtools';
|
|
7
7
|
const uid = Math.random().toString(36).slice(2, 8);
|
|
8
8
|
const isTop = position.startsWith('top');
|
|
9
|
-
const isRight = position.endsWith('right');
|
|
10
9
|
const outerPos = isTop ? 'top:0' : 'bottom:0';
|
|
11
|
-
const outerSide = isRight ? 'right:0' : 'left:0';
|
|
12
|
-
const outerBorder = isTop ? 'border-bottom:2px solid #3b82f6' : 'border-top:2px solid #3b82f6';
|
|
13
10
|
const outerShadow = isTop
|
|
14
|
-
? 'box-shadow:0
|
|
15
|
-
: 'box-shadow:0 -
|
|
16
|
-
const dark = theme === 'dark';
|
|
17
|
-
const bg = dark ? '#1f2937' : '#ffffff';
|
|
18
|
-
const fg = dark ? '#f3f4f6' : '#1f2937';
|
|
19
|
-
const panelBg = dark ? '#111827' : '#f3f4f6';
|
|
20
|
-
const borderClr = dark ? '#374151' : '#e5e7eb';
|
|
21
|
-
const btnFg = dark ? '#d1d5db' : '#374151';
|
|
22
|
-
const btnBg = dark ? '#374151' : '#e5e7eb';
|
|
23
|
-
const btnHover = dark ? '#4b5563' : '#d1d5db';
|
|
24
|
-
const dimText = dark ? '#9ca3af' : '#6b7280';
|
|
25
|
-
const dimmerText = dark ? '#6b7280' : '#9ca3af';
|
|
26
|
-
const accentText = dark ? '#60a5fa' : '#2563eb';
|
|
27
|
-
const warnClr = '#f59e0b';
|
|
28
|
-
const errClr = '#ef4444';
|
|
29
|
-
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)';
|
|
30
13
|
return `<script id="${id}">
|
|
31
14
|
(function(){
|
|
32
15
|
var D=document, B=D.body, C='__nestDt'+'${uid}';
|
|
33
16
|
var p = window.location.pathname.replace(\/[^\/]*$/,'') + '/__devtools';
|
|
34
17
|
var c=D.createElement('div'); c.id=C;
|
|
35
|
-
c.innerHTML='<div style="position:fixed;${outerPos}
|
|
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>';
|
|
36
19
|
B.appendChild(c);
|
|
37
20
|
|
|
38
21
|
var lastActive=null;
|
|
39
22
|
function renderPanel(name, detail) {
|
|
40
|
-
var
|
|
41
|
-
if (lastActive===name) {
|
|
23
|
+
var pnl=D.getElementById(C+'-panel');
|
|
24
|
+
if (lastActive===name) { pnl.style.display='none'; lastActive=null; return; }
|
|
42
25
|
lastActive=name;
|
|
43
|
-
var h='<div style="
|
|
44
|
-
h+='<
|
|
45
|
-
h+='<span style="
|
|
46
|
-
h+='<span style="color
|
|
47
|
-
h+='<span style="margin-left:auto;cursor:pointer;color:${dimmerText};font-size:16px" onclick="document.getElementById('+C+'-panel).style.display=\\'none\\';lastActive=null">\u00d7</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>';
|
|
48
30
|
h+='</div>';
|
|
49
|
-
h+='<
|
|
31
|
+
h+='<div style="padding:10px 14px 14px;overflow-y:auto;max-height:360px">';
|
|
32
|
+
var fields=[];
|
|
50
33
|
for (var k in detail.details) {
|
|
51
34
|
var v=detail.details[k];
|
|
52
|
-
if (k==='queries' && Array.isArray(v)) {
|
|
53
|
-
|
|
54
|
-
|
|
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>';
|
|
55
48
|
}
|
|
56
49
|
continue;
|
|
57
50
|
}
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
var
|
|
62
|
-
|
|
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>';
|
|
63
57
|
}
|
|
64
58
|
continue;
|
|
65
59
|
}
|
|
66
|
-
if (
|
|
67
|
-
for (var
|
|
68
|
-
var g=
|
|
69
|
-
h+='<
|
|
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>';
|
|
70
64
|
if (g.routes) {
|
|
71
|
-
for (var
|
|
72
|
-
h+='<
|
|
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>';
|
|
73
67
|
}
|
|
74
68
|
}
|
|
75
69
|
}
|
|
76
70
|
continue;
|
|
77
71
|
}
|
|
78
|
-
|
|
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>';
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
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>';
|
|
79
77
|
}
|
|
80
|
-
h+='</
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
D.getElementById(C+'-toggle').innerHTML='
|
|
78
|
+
h+='</div>';
|
|
79
|
+
pnl.innerHTML=h;
|
|
80
|
+
pnl.style.display='block';
|
|
81
|
+
D.getElementById(C+'-toggle').innerHTML='\u203a';
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
function escapeHtml(s) {
|
|
87
85
|
if (s==null) return '';
|
|
88
|
-
return String(s).replace(
|
|
86
|
+
return String(s).replace(/[&<>"]/g,function(m){return {'&':'&','<':'<','>':'>','"':'"'}[m]});
|
|
87
|
+
}
|
|
88
|
+
|
|
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;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
var furl = p + '/data';
|
|
92
100
|
fetch(furl).then(function(r){return r.json()}).then(function(d){
|
|
93
101
|
var bar=D.getElementById(C+'-bar');
|
|
94
|
-
var
|
|
95
|
-
|
|
96
|
-
if(d
|
|
97
|
-
if(d.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
var ic=icons[p.label.toLowerCase()]||icons['default'];
|
|
104
|
-
var btn=D.createElement('span');
|
|
105
|
-
btn.style.cssText='display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:4px;cursor:pointer;color:${btnFg};background:${btnBg};margin:0 2px;transition:background .15s';
|
|
106
|
-
btn.innerHTML=ic+' '+escapeHtml(p.summary);
|
|
107
|
-
btn.onmouseenter=function(){this.style.background='${btnHover}'};
|
|
108
|
-
btn.onmouseleave=function(){this.style.background='${btnBg}'};
|
|
109
|
-
btn.onclick=function(n){return function(){
|
|
110
|
-
var key=Object.keys(d).filter(function(k){return d[k]===d[n]})[0];
|
|
102
|
+
var order=['request','performance','database','routes','logs'];
|
|
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){
|
|
107
|
+
var p=item.data;
|
|
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];
|
|
111
111
|
renderPanel(key,d[key]);
|
|
112
|
-
}
|
|
113
|
-
bar.appendChild(
|
|
112
|
+
};
|
|
113
|
+
bar.appendChild(badge);
|
|
114
114
|
});
|
|
115
115
|
D.getElementById(C+'-toggle').onclick=function(){
|
|
116
116
|
var pnl=D.getElementById(C+'-panel');
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
var isOpen=pnl.style.display==='block';
|
|
118
|
+
pnl.style.display=isOpen?'none':'block';
|
|
119
|
+
this.innerHTML=isOpen?'\u2039':'\u203a';
|
|
120
|
+
if(isOpen)lastActive=null;
|
|
119
121
|
};
|
|
120
122
|
}).catch(function(){});
|
|
121
123
|
})();
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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"}
|