nestjs-profiler 1.0.24 → 1.0.26
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/README.md +167 -5
- package/collectors/event-collector.d.ts +18 -0
- package/collectors/event-collector.js +254 -0
- package/collectors/event-collector.js.map +1 -0
- package/common/profiler-options.interface.d.ts +8 -0
- package/common/profiler.model.d.ts +25 -0
- package/controllers/profiler.controller.d.ts +22 -1
- package/controllers/profiler.controller.js +161 -15
- package/controllers/profiler.controller.js.map +1 -1
- package/middleware/profiler-auth.middleware.d.ts +10 -0
- package/middleware/profiler-auth.middleware.js +110 -0
- package/middleware/profiler-auth.middleware.js.map +1 -0
- package/package.json +2 -2
- package/profiler.module.js +82 -0
- package/profiler.module.js.map +1 -1
- package/services/code-quality.service.d.ts +65 -0
- package/services/code-quality.service.js +216 -0
- package/services/code-quality.service.js.map +1 -0
- package/services/entity-explorer.service.d.ts +1 -1
- package/services/entity-explorer.service.js +7 -7
- package/services/entity-explorer.service.js.map +1 -1
- package/services/health.service.d.ts +59 -0
- package/services/health.service.js +190 -0
- package/services/health.service.js.map +1 -0
- package/services/profiler.service.d.ts +10 -1
- package/services/profiler.service.js +103 -35
- package/services/profiler.service.js.map +1 -1
- package/services/route-explorer.service.d.ts +23 -1
- package/services/route-explorer.service.js +136 -10
- package/services/route-explorer.service.js.map +1 -1
- package/services/template-builder.service.d.ts +3 -0
- package/services/template-builder.service.js +191 -84
- package/services/template-builder.service.js.map +1 -1
- package/services/view.service.d.ts +1 -1
- package/services/view.service.js +34 -23
- package/services/view.service.js.map +1 -1
- package/views/code-quality.html +573 -0
- package/views/events.html +763 -0
- package/views/health.html +468 -0
- package/views/layout.html +50 -10
- package/views/login.html +277 -0
- package/views/routes.html +405 -37
package/views/login.html
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="h-full">
|
|
3
|
+
<head>
|
|
4
|
+
<!-- If already authenticated, skip login immediately -->
|
|
5
|
+
<script>
|
|
6
|
+
(function () {
|
|
7
|
+
var stored, data;
|
|
8
|
+
try { stored = localStorage.getItem('__profiler_auth'); data = stored ? JSON.parse(stored) : null; } catch (e) { data = null; }
|
|
9
|
+
if (data && data.expiresAt && Date.now() < data.expiresAt) {
|
|
10
|
+
var params = new URLSearchParams(location.search);
|
|
11
|
+
location.replace(params.get('returnTo') || '/__profiler');
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
</script>
|
|
15
|
+
<meta charset="UTF-8" />
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
17
|
+
<title>NestJS Profiler — Sign In</title>
|
|
18
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
19
|
+
<style>
|
|
20
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
|
21
|
+
* { font-family: 'Inter', system-ui, sans-serif; }
|
|
22
|
+
body { background: #0f172a; min-height: 100vh; }
|
|
23
|
+
|
|
24
|
+
.orb { position: fixed; border-radius: 50%; filter: blur(80px); opacity: 0.15; animation: drift 12s ease-in-out infinite; pointer-events: none; }
|
|
25
|
+
.orb-1 { width: 600px; height: 600px; background: radial-gradient(circle, #6366f1, transparent 70%); top: -200px; left: -200px; }
|
|
26
|
+
.orb-2 { width: 500px; height: 500px; background: radial-gradient(circle, #818cf8, transparent 70%); bottom: -150px; right: -150px; animation-delay: -6s; }
|
|
27
|
+
@keyframes drift {
|
|
28
|
+
0%, 100% { transform: translate(0,0) scale(1); }
|
|
29
|
+
33% { transform: translate(30px,-20px) scale(1.05); }
|
|
30
|
+
66% { transform: translate(-20px,30px) scale(0.95); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.glass-card {
|
|
34
|
+
background: rgba(15,23,42,0.85);
|
|
35
|
+
backdrop-filter: blur(24px);
|
|
36
|
+
border: 1px solid rgba(99,102,241,0.2);
|
|
37
|
+
box-shadow: 0 0 0 1px rgba(255,255,255,0.04) inset, 0 32px 64px rgba(0,0,0,0.6), 0 0 80px rgba(99,102,241,0.08);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.auth-input {
|
|
41
|
+
background: rgba(15,23,42,0.7);
|
|
42
|
+
border: 1px solid rgba(71,85,105,0.6);
|
|
43
|
+
color: #e2e8f0;
|
|
44
|
+
transition: border-color .2s, box-shadow .2s, background .2s;
|
|
45
|
+
}
|
|
46
|
+
.auth-input::placeholder { color: #475569; }
|
|
47
|
+
.auth-input:focus {
|
|
48
|
+
outline: none;
|
|
49
|
+
border-color: #6366f1;
|
|
50
|
+
background: rgba(15,23,42,0.9);
|
|
51
|
+
box-shadow: 0 0 0 3px rgba(99,102,241,0.15), 0 1px 3px rgba(0,0,0,0.3);
|
|
52
|
+
}
|
|
53
|
+
.auth-input.error { border-color: rgba(239,68,68,0.6); }
|
|
54
|
+
|
|
55
|
+
.btn-primary {
|
|
56
|
+
background: linear-gradient(135deg, #6366f1 0%, #818cf8 100%);
|
|
57
|
+
box-shadow: 0 4px 16px rgba(99,102,241,0.35), 0 1px 3px rgba(0,0,0,0.3);
|
|
58
|
+
transition: all .2s;
|
|
59
|
+
}
|
|
60
|
+
.btn-primary:hover:not(:disabled) {
|
|
61
|
+
background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
|
|
62
|
+
box-shadow: 0 6px 24px rgba(99,102,241,0.5), 0 2px 6px rgba(0,0,0,0.4);
|
|
63
|
+
transform: translateY(-1px);
|
|
64
|
+
}
|
|
65
|
+
.btn-primary:active:not(:disabled) { transform: translateY(0); }
|
|
66
|
+
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
67
|
+
|
|
68
|
+
.logo-ring {
|
|
69
|
+
background: rgba(99,102,241,0.1);
|
|
70
|
+
border: 1px solid rgba(99,102,241,0.25);
|
|
71
|
+
box-shadow: 0 0 32px rgba(99,102,241,0.15);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes shake {
|
|
75
|
+
0%,100% { transform: translateX(0); }
|
|
76
|
+
20% { transform: translateX(-6px); }
|
|
77
|
+
40% { transform: translateX(6px); }
|
|
78
|
+
60% { transform: translateX(-4px); }
|
|
79
|
+
80% { transform: translateX(4px); }
|
|
80
|
+
}
|
|
81
|
+
.shake { animation: shake 0.4s ease; }
|
|
82
|
+
|
|
83
|
+
.spinner { width:18px; height:18px; border:2px solid rgba(255,255,255,0.3); border-top-color:#fff; border-radius:50%; animation:spin .7s linear infinite; }
|
|
84
|
+
@keyframes spin { to { transform:rotate(360deg); } }
|
|
85
|
+
</style>
|
|
86
|
+
</head>
|
|
87
|
+
<body class="h-full flex items-center justify-center p-4">
|
|
88
|
+
|
|
89
|
+
<div class="orb orb-1"></div>
|
|
90
|
+
<div class="orb orb-2"></div>
|
|
91
|
+
|
|
92
|
+
<div class="glass-card rounded-2xl w-full max-w-md p-10 relative z-10" id="card">
|
|
93
|
+
|
|
94
|
+
<!-- Logo -->
|
|
95
|
+
<div class="flex flex-col items-center mb-10">
|
|
96
|
+
<div class="logo-ring w-20 h-20 rounded-2xl flex items-center justify-center mb-5">
|
|
97
|
+
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
98
|
+
<rect width="44" height="44" rx="10" fill="rgba(99,102,241,0.15)"/>
|
|
99
|
+
<circle cx="19" cy="18" r="8" stroke="#818cf8" stroke-width="2.5" fill="none"/>
|
|
100
|
+
<line x1="24.5" y1="24.5" x2="32" y2="32" stroke="#818cf8" stroke-width="2.5" stroke-linecap="round"/>
|
|
101
|
+
<circle cx="19" cy="18" r="3" fill="#6366f1" opacity="0.8"/>
|
|
102
|
+
<rect x="10" y="32" width="3" height="5" rx="1" fill="#6366f1" opacity="0.6"/>
|
|
103
|
+
<rect x="15" y="29" width="3" height="8" rx="1" fill="#6366f1" opacity="0.8"/>
|
|
104
|
+
<rect x="20" y="31" width="3" height="6" rx="1" fill="#818cf8" opacity="0.7"/>
|
|
105
|
+
<rect x="25" y="27" width="3" height="10" rx="1" fill="#818cf8" opacity="0.9"/>
|
|
106
|
+
<rect x="30" y="30" width="3" height="7" rx="1" fill="#6366f1" opacity="0.6"/>
|
|
107
|
+
</svg>
|
|
108
|
+
</div>
|
|
109
|
+
<h1 class="text-2xl font-bold text-white tracking-tight">NestJS Profiler</h1>
|
|
110
|
+
<p class="text-slate-400 text-sm mt-1.5 text-center">This area is protected. Sign in to continue.</p>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Error banner -->
|
|
114
|
+
<div id="errorBanner" class="hidden mb-6 rounded-xl bg-red-500/10 border border-red-500/30 px-4 py-3 flex items-start gap-3">
|
|
115
|
+
<svg class="w-5 h-5 text-red-400 mt-0.5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
116
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
|
|
117
|
+
</svg>
|
|
118
|
+
<span id="errorMsg" class="text-red-300 text-sm"></span>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- Form -->
|
|
122
|
+
<div id="loginForm">
|
|
123
|
+
<!-- Username -->
|
|
124
|
+
<div class="mb-5">
|
|
125
|
+
<label for="username" class="block text-sm font-medium text-slate-300 mb-2">Username</label>
|
|
126
|
+
<div class="relative">
|
|
127
|
+
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none">
|
|
128
|
+
<svg class="w-4 h-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
129
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"/>
|
|
130
|
+
</svg>
|
|
131
|
+
</div>
|
|
132
|
+
<input id="username" type="text" autocomplete="username" placeholder="Enter your username"
|
|
133
|
+
class="auth-input w-full rounded-xl pl-10 pr-4 py-3 text-sm" />
|
|
134
|
+
</div>
|
|
135
|
+
<p id="usernameError" class="mt-1.5 text-xs text-red-400 hidden">Username is required.</p>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Password -->
|
|
139
|
+
<div class="mb-8">
|
|
140
|
+
<label for="password" class="block text-sm font-medium text-slate-300 mb-2">Password</label>
|
|
141
|
+
<div class="relative">
|
|
142
|
+
<div class="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none">
|
|
143
|
+
<svg class="w-4 h-4 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
144
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/>
|
|
145
|
+
</svg>
|
|
146
|
+
</div>
|
|
147
|
+
<input id="password" type="password" autocomplete="current-password" placeholder="Enter your password"
|
|
148
|
+
class="auth-input w-full rounded-xl pl-10 pr-12 py-3 text-sm" />
|
|
149
|
+
<button type="button" id="togglePassword"
|
|
150
|
+
class="absolute inset-y-0 right-0 pr-3.5 flex items-center text-slate-500 hover:text-slate-300 transition-colors">
|
|
151
|
+
<svg id="eyeIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
152
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.964-7.178z"/>
|
|
153
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
154
|
+
</svg>
|
|
155
|
+
<svg id="eyeSlashIcon" class="w-4 h-4 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
156
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/>
|
|
157
|
+
</svg>
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
<p id="passwordError" class="mt-1.5 text-xs text-red-400 hidden">Password is required.</p>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Submit -->
|
|
164
|
+
<button id="submitBtn" type="button"
|
|
165
|
+
class="btn-primary w-full rounded-xl py-3.5 text-sm font-semibold text-white flex items-center justify-center gap-2">
|
|
166
|
+
<span id="btnText">Sign In</span>
|
|
167
|
+
<div id="btnSpinner" class="spinner hidden"></div>
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<p class="text-center text-xs text-slate-600 mt-8">NestJS Profiler — RASLAN</p>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<script>
|
|
175
|
+
(function () {
|
|
176
|
+
var returnTo = (new URLSearchParams(location.search)).get('returnTo') || '/__profiler';
|
|
177
|
+
|
|
178
|
+
// ── Toggle password visibility ──────────────────────────────────────────
|
|
179
|
+
var pwdInput = document.getElementById('password');
|
|
180
|
+
var eyeIcon = document.getElementById('eyeIcon');
|
|
181
|
+
var eyeSlash = document.getElementById('eyeSlashIcon');
|
|
182
|
+
document.getElementById('togglePassword').addEventListener('click', function () {
|
|
183
|
+
var show = pwdInput.type === 'password';
|
|
184
|
+
pwdInput.type = show ? 'text' : 'password';
|
|
185
|
+
eyeIcon.classList.toggle('hidden', show);
|
|
186
|
+
eyeSlash.classList.toggle('hidden', !show);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
190
|
+
function showError(msg) {
|
|
191
|
+
var banner = document.getElementById('errorBanner');
|
|
192
|
+
document.getElementById('errorMsg').textContent = msg;
|
|
193
|
+
banner.classList.remove('hidden');
|
|
194
|
+
}
|
|
195
|
+
function clearErrors() {
|
|
196
|
+
document.getElementById('errorBanner').classList.add('hidden');
|
|
197
|
+
document.getElementById('usernameError').classList.add('hidden');
|
|
198
|
+
document.getElementById('passwordError').classList.add('hidden');
|
|
199
|
+
document.getElementById('username').classList.remove('error');
|
|
200
|
+
document.getElementById('password').classList.remove('error');
|
|
201
|
+
}
|
|
202
|
+
function setLoading(on) {
|
|
203
|
+
document.getElementById('btnText').textContent = on ? 'Signing in…' : 'Sign In';
|
|
204
|
+
document.getElementById('btnSpinner').classList.toggle('hidden', !on);
|
|
205
|
+
document.getElementById('submitBtn').disabled = on;
|
|
206
|
+
}
|
|
207
|
+
function shake() {
|
|
208
|
+
var card = document.getElementById('card');
|
|
209
|
+
card.classList.remove('shake');
|
|
210
|
+
void card.offsetWidth;
|
|
211
|
+
card.classList.add('shake');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── Submit ───────────────────────────────────────────────────────────────
|
|
215
|
+
document.getElementById('submitBtn').addEventListener('click', function () {
|
|
216
|
+
clearErrors();
|
|
217
|
+
var username = document.getElementById('username').value.trim();
|
|
218
|
+
var password = document.getElementById('password').value;
|
|
219
|
+
var valid = true;
|
|
220
|
+
|
|
221
|
+
if (!username) {
|
|
222
|
+
document.getElementById('usernameError').classList.remove('hidden');
|
|
223
|
+
document.getElementById('username').classList.add('error');
|
|
224
|
+
valid = false;
|
|
225
|
+
}
|
|
226
|
+
if (!password) {
|
|
227
|
+
document.getElementById('passwordError').classList.remove('hidden');
|
|
228
|
+
document.getElementById('password').classList.add('error');
|
|
229
|
+
valid = false;
|
|
230
|
+
}
|
|
231
|
+
if (!valid) { shake(); return; }
|
|
232
|
+
|
|
233
|
+
setLoading(true);
|
|
234
|
+
|
|
235
|
+
fetch('/__profiler/api/login', {
|
|
236
|
+
method: 'POST',
|
|
237
|
+
headers: { 'Content-Type': 'application/json' },
|
|
238
|
+
body: JSON.stringify({ username: username, password: password }),
|
|
239
|
+
})
|
|
240
|
+
.then(function (r) { return r.json().then(function (d) { return { status: r.status, data: d }; }); })
|
|
241
|
+
.then(function (result) {
|
|
242
|
+
if (result.status === 200 || result.status === 201) {
|
|
243
|
+
// Store token in localStorage
|
|
244
|
+
localStorage.setItem('__profiler_auth', JSON.stringify({
|
|
245
|
+
token: result.data.token,
|
|
246
|
+
expiresAt: result.data.expiresAt,
|
|
247
|
+
username: result.data.username || username,
|
|
248
|
+
}));
|
|
249
|
+
location.replace(returnTo);
|
|
250
|
+
} else {
|
|
251
|
+
setLoading(false);
|
|
252
|
+
showError(result.data.error || 'Invalid username or password.');
|
|
253
|
+
shake();
|
|
254
|
+
document.getElementById('password').value = '';
|
|
255
|
+
document.getElementById('password').focus();
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
.catch(function () {
|
|
259
|
+
setLoading(false);
|
|
260
|
+
showError('Network error — could not reach the server.');
|
|
261
|
+
shake();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Allow Enter key to submit
|
|
266
|
+
['username', 'password'].forEach(function (id) {
|
|
267
|
+
document.getElementById(id).addEventListener('keydown', function (e) {
|
|
268
|
+
if (e.key === 'Enter') document.getElementById('submitBtn').click();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Auto-focus
|
|
273
|
+
document.getElementById('username').focus();
|
|
274
|
+
})();
|
|
275
|
+
</script>
|
|
276
|
+
</body>
|
|
277
|
+
</html>
|