@seip/blue-bird 0.3.3 → 0.3.4
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/.env_example +23 -13
- package/LICENSE +21 -21
- package/README.md +79 -79
- package/backend/index.js +12 -12
- package/backend/routes/api.js +34 -34
- package/backend/routes/frontend.js +1 -8
- package/core/app.js +359 -359
- package/core/auth.js +69 -69
- package/core/cache.js +35 -35
- package/core/cli/component.js +42 -42
- package/core/cli/init.js +120 -118
- package/core/cli/react.js +383 -411
- package/core/cli/route.js +42 -42
- package/core/cli/scaffolding-auth.js +967 -0
- package/core/config.js +41 -41
- package/core/debug.js +248 -248
- package/core/logger.js +80 -80
- package/core/middleware.js +27 -27
- package/core/router.js +134 -134
- package/core/swagger.js +24 -24
- package/core/template.js +288 -288
- package/core/upload.js +76 -76
- package/core/validate.js +291 -290
- package/frontend/index.html +28 -22
- package/frontend/resources/js/App.jsx +28 -42
- package/frontend/resources/js/Main.jsx +17 -17
- package/frontend/resources/js/blue-bird/components/Button.jsx +67 -0
- package/frontend/resources/js/blue-bird/components/Card.jsx +17 -0
- package/frontend/resources/js/blue-bird/components/DataTable.jsx +126 -0
- package/frontend/resources/js/blue-bird/components/Input.jsx +21 -0
- package/frontend/resources/js/blue-bird/components/Label.jsx +12 -0
- package/frontend/resources/js/blue-bird/components/Modal.jsx +27 -0
- package/frontend/resources/js/blue-bird/components/Translate.jsx +12 -0
- package/frontend/resources/js/blue-bird/components/Typography.jsx +25 -0
- package/frontend/resources/js/blue-bird/contexts/LanguageContext.jsx +29 -0
- package/frontend/resources/js/blue-bird/contexts/SnackbarContext.jsx +38 -0
- package/frontend/resources/js/blue-bird/contexts/ThemeContext.jsx +49 -0
- package/frontend/resources/js/blue-bird/locales/en.json +30 -0
- package/frontend/resources/js/blue-bird/locales/es.json +30 -0
- package/frontend/resources/js/pages/About.jsx +33 -15
- package/frontend/resources/js/pages/Home.jsx +93 -68
- package/package.json +56 -55
- package/vite.config.js +21 -21
package/core/config.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Configuration class to manage application-wide settings and environment variables.
|
|
5
|
-
*/
|
|
6
|
-
class Config {
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Returns the base directory of the application.
|
|
10
|
-
* @returns {string} The current working directory.
|
|
11
|
-
*/
|
|
12
|
-
static dirname() {
|
|
13
|
-
return process.cwd()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Retrieves application properties from environment variables or default values.
|
|
18
|
-
* @returns {Object} The configuration properties object.
|
|
19
|
-
*/
|
|
20
|
-
static props() {
|
|
21
|
-
const config = {
|
|
22
|
-
debug: process.env.DEBUG === "true" ? true : false,
|
|
23
|
-
descriptionMeta: process.env.DESCRIPTION_META || "",
|
|
24
|
-
keywordsMeta: process.env.KEYWORDS_META || "",
|
|
25
|
-
titleMeta: process.env.TITLE_META || "",
|
|
26
|
-
authorMeta: process.env.AUTHOR_META || "",
|
|
27
|
-
description: process.env.DESCRIPTION || "",
|
|
28
|
-
title: process.env.TITLE || "",
|
|
29
|
-
version: process.env.VERSION || "1.0.0",
|
|
30
|
-
langMeta: process.env.LANGMETA || "en",
|
|
31
|
-
host: process.env.HOST || "http://localhost",
|
|
32
|
-
port: parseInt(process.env.PORT) || 3001,
|
|
33
|
-
static: {
|
|
34
|
-
path: process.env.STATIC_PATH || "frontend/public",
|
|
35
|
-
options: {}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
return config
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration class to manage application-wide settings and environment variables.
|
|
5
|
+
*/
|
|
6
|
+
class Config {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the base directory of the application.
|
|
10
|
+
* @returns {string} The current working directory.
|
|
11
|
+
*/
|
|
12
|
+
static dirname() {
|
|
13
|
+
return process.cwd()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves application properties from environment variables or default values.
|
|
18
|
+
* @returns {Object} The configuration properties object.
|
|
19
|
+
*/
|
|
20
|
+
static props() {
|
|
21
|
+
const config = {
|
|
22
|
+
debug: process.env.DEBUG === "true" ? true : false,
|
|
23
|
+
descriptionMeta: process.env.DESCRIPTION_META || "",
|
|
24
|
+
keywordsMeta: process.env.KEYWORDS_META || "",
|
|
25
|
+
titleMeta: process.env.TITLE_META || "",
|
|
26
|
+
authorMeta: process.env.AUTHOR_META || "",
|
|
27
|
+
description: process.env.DESCRIPTION || "",
|
|
28
|
+
title: process.env.TITLE || "",
|
|
29
|
+
version: process.env.VERSION || "1.0.0",
|
|
30
|
+
langMeta: process.env.LANGMETA || "en",
|
|
31
|
+
host: process.env.HOST || "http://localhost",
|
|
32
|
+
port: parseInt(process.env.PORT) || 3001,
|
|
33
|
+
static: {
|
|
34
|
+
path: process.env.STATIC_PATH || "frontend/public",
|
|
35
|
+
options: {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
return config
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
42
|
export default Config
|
package/core/debug.js
CHANGED
|
@@ -1,249 +1,249 @@
|
|
|
1
|
-
import Router from "./router.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Advanced Debug module for Blue Bird.
|
|
5
|
-
* Provides metrics history, route statistics and live monitoring.
|
|
6
|
-
*/
|
|
7
|
-
class Debug {
|
|
8
|
-
|
|
9
|
-
constructor() {
|
|
10
|
-
this.router = new Router("/debug");
|
|
11
|
-
this.limit = 50;
|
|
12
|
-
this.initStore();
|
|
13
|
-
this.registerRoutes();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
initStore() {
|
|
17
|
-
if (!global.__bluebird_debug_store__) {
|
|
18
|
-
global.__bluebird_debug_store__ = {
|
|
19
|
-
requests: [],
|
|
20
|
-
routes: {},
|
|
21
|
-
errors4xx: 0,
|
|
22
|
-
errors5xx: 0
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static shouldTrack(req) {
|
|
28
|
-
const url = req.originalUrl || "";
|
|
29
|
-
|
|
30
|
-
if (url.startsWith("/debug")) return false;
|
|
31
|
-
|
|
32
|
-
if (/\.(json|css|js|map|png|jpg|jpeg|gif|svg|ico|webp)$/i.test(url)) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (req.method === "OPTIONS") return false;
|
|
37
|
-
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
static middlewareMetrics(app) {
|
|
42
|
-
app.use((req, res, next) => {
|
|
43
|
-
|
|
44
|
-
if (!Debug.shouldTrack(req)) return next();
|
|
45
|
-
|
|
46
|
-
const start = process.hrtime();
|
|
47
|
-
|
|
48
|
-
res.on("finish", () => {
|
|
49
|
-
|
|
50
|
-
const diff = process.hrtime(start);
|
|
51
|
-
const responseTime = diff[0] * 1e3 + diff[1] / 1e6;
|
|
52
|
-
|
|
53
|
-
const memory = process.memoryUsage();
|
|
54
|
-
const ramUsedMB = memory.rss / 1024 / 1024;
|
|
55
|
-
|
|
56
|
-
const cpuUsage = process.cpuUsage();
|
|
57
|
-
const cpuUsedMS = (cpuUsage.user + cpuUsage.system) / 1000;
|
|
58
|
-
|
|
59
|
-
const record = {
|
|
60
|
-
method: req.method,
|
|
61
|
-
url: req.originalUrl,
|
|
62
|
-
status: res.statusCode,
|
|
63
|
-
responseTime: Number(responseTime.toFixed(2)),
|
|
64
|
-
ramUsedMB: Number(ramUsedMB.toFixed(2)),
|
|
65
|
-
cpuUsedMS: Number(cpuUsedMS.toFixed(2)),
|
|
66
|
-
date: new Date().toISOString()
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const store = global.__bluebird_debug_store__;
|
|
70
|
-
|
|
71
|
-
store.requests.unshift(record);
|
|
72
|
-
if (store.requests.length > 50) store.requests.pop();
|
|
73
|
-
|
|
74
|
-
const routeKey = `${req.method} ${req.route?.path || req.path}`;
|
|
75
|
-
|
|
76
|
-
if (!store.routes[routeKey]) {
|
|
77
|
-
store.routes[routeKey] = {
|
|
78
|
-
count: 0,
|
|
79
|
-
totalTime: 0
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
store.routes[routeKey].count += 1;
|
|
84
|
-
store.routes[routeKey].totalTime += record.responseTime;
|
|
85
|
-
|
|
86
|
-
if (record.status >= 400 && record.status < 500) store.errors4xx++;
|
|
87
|
-
if (record.status >= 500) store.errors5xx++;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
next();
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
registerRoutes() {
|
|
95
|
-
|
|
96
|
-
this.router.get("/", (req, res) => {
|
|
97
|
-
|
|
98
|
-
const store = global.__bluebird_debug_store__;
|
|
99
|
-
|
|
100
|
-
if (req.query.fetch === "true") {
|
|
101
|
-
return res.json(store);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (req.query.reset === "true") {
|
|
105
|
-
global.__bluebird_debug_store__ = {
|
|
106
|
-
requests: [],
|
|
107
|
-
routes: {},
|
|
108
|
-
errors4xx: 0,
|
|
109
|
-
errors5xx: 0
|
|
110
|
-
};
|
|
111
|
-
return res.json({ ok: true });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
res.send(`
|
|
115
|
-
<!DOCTYPE html>
|
|
116
|
-
<html>
|
|
117
|
-
<head>
|
|
118
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
119
|
-
<title>Blue Bird Debug</title>
|
|
120
|
-
</head>
|
|
121
|
-
<body class="bg-gray-100 text-gray-800 p-10">
|
|
122
|
-
|
|
123
|
-
<div class="max-w-7xl mx-auto">
|
|
124
|
-
|
|
125
|
-
<div class="flex justify-between items-center mb-8">
|
|
126
|
-
<h1 class="text-3xl font-bold text-blue-600">Blue Bird Debug Panel</h1>
|
|
127
|
-
<button onclick="resetData()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg shadow">
|
|
128
|
-
Reset
|
|
129
|
-
</button>
|
|
130
|
-
</div>
|
|
131
|
-
|
|
132
|
-
<div class="grid grid-cols-3 gap-6 mb-8">
|
|
133
|
-
|
|
134
|
-
<div class="bg-white p-6 rounded-xl shadow">
|
|
135
|
-
<h3 class="text-gray-500 text-sm">Total Requests</h3>
|
|
136
|
-
<p id="totalReq" class="text-2xl font-bold">0</p>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<div class="bg-yellow-100 p-6 rounded-xl shadow">
|
|
140
|
-
<h3 class="text-yellow-600 text-sm">4xx Errors</h3>
|
|
141
|
-
<p id="err4" class="text-2xl font-bold text-yellow-700">0</p>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<div class="bg-red-100 p-6 rounded-xl shadow">
|
|
145
|
-
<h3 class="text-red-600 text-sm">5xx Errors</h3>
|
|
146
|
-
<p id="err5" class="text-2xl font-bold text-red-700">0</p>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
<div class="grid grid-cols-2 gap-10">
|
|
152
|
-
|
|
153
|
-
<div class="bg-white p-6 rounded-xl shadow">
|
|
154
|
-
<h2 class="text-lg font-semibold mb-4">Route Stats</h2>
|
|
155
|
-
<table class="table-fixed w-full text-sm">
|
|
156
|
-
<thead>
|
|
157
|
-
<tr class="border-b">
|
|
158
|
-
<th class="text-left w-1/2 py-2">Route</th>
|
|
159
|
-
<th class="text-left w-1/4">Hits</th>
|
|
160
|
-
<th class="text-left w-1/4">Avg Time</th>
|
|
161
|
-
</tr>
|
|
162
|
-
</thead>
|
|
163
|
-
<tbody id="routesBody"></tbody>
|
|
164
|
-
</table>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<div class="bg-white p-6 rounded-xl shadow">
|
|
168
|
-
<h2 class="text-lg font-semibold mb-4">Last Requests</h2>
|
|
169
|
-
<table class="table-fixed w-full text-sm">
|
|
170
|
-
<thead>
|
|
171
|
-
<tr class="border-b">
|
|
172
|
-
<th class="text-left w-1/2 py-2">URL</th>
|
|
173
|
-
<th class="text-left w-1/6">Method</th>
|
|
174
|
-
<th class="text-left w-1/6">Status</th>
|
|
175
|
-
<th class="text-left w-1/6">Time</th>
|
|
176
|
-
</tr>
|
|
177
|
-
</thead>
|
|
178
|
-
<tbody id="historyBody"></tbody>
|
|
179
|
-
</table>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
<script>
|
|
187
|
-
async function loadData() {
|
|
188
|
-
const res = await fetch('/debug?fetch=true');
|
|
189
|
-
const data = await res.json();
|
|
190
|
-
|
|
191
|
-
document.getElementById("totalReq").innerText = data.requests.length;
|
|
192
|
-
document.getElementById("err4").innerText = data.errors4xx;
|
|
193
|
-
document.getElementById("err5").innerText = data.errors5xx;
|
|
194
|
-
|
|
195
|
-
const routesBody = document.getElementById("routesBody");
|
|
196
|
-
const historyBody = document.getElementById("historyBody");
|
|
197
|
-
|
|
198
|
-
routesBody.innerHTML = "";
|
|
199
|
-
historyBody.innerHTML = "";
|
|
200
|
-
|
|
201
|
-
Object.entries(data.routes).forEach(([key, value]) => {
|
|
202
|
-
const avg = (value.totalTime / value.count).toFixed(2);
|
|
203
|
-
let color = "";
|
|
204
|
-
if (avg > 500) color = "text-red-600 font-semibold";
|
|
205
|
-
else if (avg > 200) color = "text-yellow-600";
|
|
206
|
-
|
|
207
|
-
routesBody.innerHTML += \`
|
|
208
|
-
<tr class="border-b">
|
|
209
|
-
<td class="py-1 truncate">\${key}</td>
|
|
210
|
-
<td>\${value.count}</td>
|
|
211
|
-
<td class="\${color}">\${avg} ms</td>
|
|
212
|
-
</tr>\`;
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
data.requests.forEach(r => {
|
|
216
|
-
historyBody.innerHTML += \`
|
|
217
|
-
<tr class="border-b text-xs">
|
|
218
|
-
<td class="truncate">\${r.url}</td>
|
|
219
|
-
<td>\${r.method}</td>
|
|
220
|
-
<td>\${r.status}</td>
|
|
221
|
-
<td>\${r.responseTime} ms</td>
|
|
222
|
-
</tr>\`;
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async function resetData() {
|
|
227
|
-
await fetch('/debug?reset=true');
|
|
228
|
-
loadData();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
loadData();
|
|
232
|
-
setInterval(loadData, 3000);
|
|
233
|
-
</script>
|
|
234
|
-
|
|
235
|
-
</body>
|
|
236
|
-
</html>
|
|
237
|
-
`);
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
getRouter() {
|
|
242
|
-
return {
|
|
243
|
-
path: this.router.getPath(),
|
|
244
|
-
router: this.router.getRouter()
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
1
|
+
import Router from "./router.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Advanced Debug module for Blue Bird.
|
|
5
|
+
* Provides metrics history, route statistics and live monitoring.
|
|
6
|
+
*/
|
|
7
|
+
class Debug {
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.router = new Router("/debug");
|
|
11
|
+
this.limit = 50;
|
|
12
|
+
this.initStore();
|
|
13
|
+
this.registerRoutes();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
initStore() {
|
|
17
|
+
if (!global.__bluebird_debug_store__) {
|
|
18
|
+
global.__bluebird_debug_store__ = {
|
|
19
|
+
requests: [],
|
|
20
|
+
routes: {},
|
|
21
|
+
errors4xx: 0,
|
|
22
|
+
errors5xx: 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static shouldTrack(req) {
|
|
28
|
+
const url = req.originalUrl || "";
|
|
29
|
+
|
|
30
|
+
if (url.startsWith("/debug")) return false;
|
|
31
|
+
|
|
32
|
+
if (/\.(json|css|js|map|png|jpg|jpeg|gif|svg|ico|webp)$/i.test(url)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (req.method === "OPTIONS") return false;
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static middlewareMetrics(app) {
|
|
42
|
+
app.use((req, res, next) => {
|
|
43
|
+
|
|
44
|
+
if (!Debug.shouldTrack(req)) return next();
|
|
45
|
+
|
|
46
|
+
const start = process.hrtime();
|
|
47
|
+
|
|
48
|
+
res.on("finish", () => {
|
|
49
|
+
|
|
50
|
+
const diff = process.hrtime(start);
|
|
51
|
+
const responseTime = diff[0] * 1e3 + diff[1] / 1e6;
|
|
52
|
+
|
|
53
|
+
const memory = process.memoryUsage();
|
|
54
|
+
const ramUsedMB = memory.rss / 1024 / 1024;
|
|
55
|
+
|
|
56
|
+
const cpuUsage = process.cpuUsage();
|
|
57
|
+
const cpuUsedMS = (cpuUsage.user + cpuUsage.system) / 1000;
|
|
58
|
+
|
|
59
|
+
const record = {
|
|
60
|
+
method: req.method,
|
|
61
|
+
url: req.originalUrl,
|
|
62
|
+
status: res.statusCode,
|
|
63
|
+
responseTime: Number(responseTime.toFixed(2)),
|
|
64
|
+
ramUsedMB: Number(ramUsedMB.toFixed(2)),
|
|
65
|
+
cpuUsedMS: Number(cpuUsedMS.toFixed(2)),
|
|
66
|
+
date: new Date().toISOString()
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const store = global.__bluebird_debug_store__;
|
|
70
|
+
|
|
71
|
+
store.requests.unshift(record);
|
|
72
|
+
if (store.requests.length > 50) store.requests.pop();
|
|
73
|
+
|
|
74
|
+
const routeKey = `${req.method} ${req.route?.path || req.path}`;
|
|
75
|
+
|
|
76
|
+
if (!store.routes[routeKey]) {
|
|
77
|
+
store.routes[routeKey] = {
|
|
78
|
+
count: 0,
|
|
79
|
+
totalTime: 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
store.routes[routeKey].count += 1;
|
|
84
|
+
store.routes[routeKey].totalTime += record.responseTime;
|
|
85
|
+
|
|
86
|
+
if (record.status >= 400 && record.status < 500) store.errors4xx++;
|
|
87
|
+
if (record.status >= 500) store.errors5xx++;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
next();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
registerRoutes() {
|
|
95
|
+
|
|
96
|
+
this.router.get("/", (req, res) => {
|
|
97
|
+
|
|
98
|
+
const store = global.__bluebird_debug_store__;
|
|
99
|
+
|
|
100
|
+
if (req.query.fetch === "true") {
|
|
101
|
+
return res.json(store);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (req.query.reset === "true") {
|
|
105
|
+
global.__bluebird_debug_store__ = {
|
|
106
|
+
requests: [],
|
|
107
|
+
routes: {},
|
|
108
|
+
errors4xx: 0,
|
|
109
|
+
errors5xx: 0
|
|
110
|
+
};
|
|
111
|
+
return res.json({ ok: true });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
res.send(`
|
|
115
|
+
<!DOCTYPE html>
|
|
116
|
+
<html>
|
|
117
|
+
<head>
|
|
118
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
119
|
+
<title>Blue Bird Debug</title>
|
|
120
|
+
</head>
|
|
121
|
+
<body class="bg-gray-100 text-gray-800 p-10">
|
|
122
|
+
|
|
123
|
+
<div class="max-w-7xl mx-auto">
|
|
124
|
+
|
|
125
|
+
<div class="flex justify-between items-center mb-8">
|
|
126
|
+
<h1 class="text-3xl font-bold text-blue-600">Blue Bird Debug Panel</h1>
|
|
127
|
+
<button onclick="resetData()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg shadow">
|
|
128
|
+
Reset
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div class="grid grid-cols-3 gap-6 mb-8">
|
|
133
|
+
|
|
134
|
+
<div class="bg-white p-6 rounded-xl shadow">
|
|
135
|
+
<h3 class="text-gray-500 text-sm">Total Requests</h3>
|
|
136
|
+
<p id="totalReq" class="text-2xl font-bold">0</p>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="bg-yellow-100 p-6 rounded-xl shadow">
|
|
140
|
+
<h3 class="text-yellow-600 text-sm">4xx Errors</h3>
|
|
141
|
+
<p id="err4" class="text-2xl font-bold text-yellow-700">0</p>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div class="bg-red-100 p-6 rounded-xl shadow">
|
|
145
|
+
<h3 class="text-red-600 text-sm">5xx Errors</h3>
|
|
146
|
+
<p id="err5" class="text-2xl font-bold text-red-700">0</p>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="grid grid-cols-2 gap-10">
|
|
152
|
+
|
|
153
|
+
<div class="bg-white p-6 rounded-xl shadow">
|
|
154
|
+
<h2 class="text-lg font-semibold mb-4">Route Stats</h2>
|
|
155
|
+
<table class="table-fixed w-full text-sm">
|
|
156
|
+
<thead>
|
|
157
|
+
<tr class="border-b">
|
|
158
|
+
<th class="text-left w-1/2 py-2">Route</th>
|
|
159
|
+
<th class="text-left w-1/4">Hits</th>
|
|
160
|
+
<th class="text-left w-1/4">Avg Time</th>
|
|
161
|
+
</tr>
|
|
162
|
+
</thead>
|
|
163
|
+
<tbody id="routesBody"></tbody>
|
|
164
|
+
</table>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div class="bg-white p-6 rounded-xl shadow">
|
|
168
|
+
<h2 class="text-lg font-semibold mb-4">Last Requests</h2>
|
|
169
|
+
<table class="table-fixed w-full text-sm">
|
|
170
|
+
<thead>
|
|
171
|
+
<tr class="border-b">
|
|
172
|
+
<th class="text-left w-1/2 py-2">URL</th>
|
|
173
|
+
<th class="text-left w-1/6">Method</th>
|
|
174
|
+
<th class="text-left w-1/6">Status</th>
|
|
175
|
+
<th class="text-left w-1/6">Time</th>
|
|
176
|
+
</tr>
|
|
177
|
+
</thead>
|
|
178
|
+
<tbody id="historyBody"></tbody>
|
|
179
|
+
</table>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<script>
|
|
187
|
+
async function loadData() {
|
|
188
|
+
const res = await fetch('/debug?fetch=true');
|
|
189
|
+
const data = await res.json();
|
|
190
|
+
|
|
191
|
+
document.getElementById("totalReq").innerText = data.requests.length;
|
|
192
|
+
document.getElementById("err4").innerText = data.errors4xx;
|
|
193
|
+
document.getElementById("err5").innerText = data.errors5xx;
|
|
194
|
+
|
|
195
|
+
const routesBody = document.getElementById("routesBody");
|
|
196
|
+
const historyBody = document.getElementById("historyBody");
|
|
197
|
+
|
|
198
|
+
routesBody.innerHTML = "";
|
|
199
|
+
historyBody.innerHTML = "";
|
|
200
|
+
|
|
201
|
+
Object.entries(data.routes).forEach(([key, value]) => {
|
|
202
|
+
const avg = (value.totalTime / value.count).toFixed(2);
|
|
203
|
+
let color = "";
|
|
204
|
+
if (avg > 500) color = "text-red-600 font-semibold";
|
|
205
|
+
else if (avg > 200) color = "text-yellow-600";
|
|
206
|
+
|
|
207
|
+
routesBody.innerHTML += \`
|
|
208
|
+
<tr class="border-b">
|
|
209
|
+
<td class="py-1 truncate">\${key}</td>
|
|
210
|
+
<td>\${value.count}</td>
|
|
211
|
+
<td class="\${color}">\${avg} ms</td>
|
|
212
|
+
</tr>\`;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
data.requests.forEach(r => {
|
|
216
|
+
historyBody.innerHTML += \`
|
|
217
|
+
<tr class="border-b text-xs">
|
|
218
|
+
<td class="truncate">\${r.url}</td>
|
|
219
|
+
<td>\${r.method}</td>
|
|
220
|
+
<td>\${r.status}</td>
|
|
221
|
+
<td>\${r.responseTime} ms</td>
|
|
222
|
+
</tr>\`;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function resetData() {
|
|
227
|
+
await fetch('/debug?reset=true');
|
|
228
|
+
loadData();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
loadData();
|
|
232
|
+
setInterval(loadData, 3000);
|
|
233
|
+
</script>
|
|
234
|
+
|
|
235
|
+
</body>
|
|
236
|
+
</html>
|
|
237
|
+
`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getRouter() {
|
|
242
|
+
return {
|
|
243
|
+
path: this.router.getPath(),
|
|
244
|
+
router: this.router.getRouter()
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
249
|
export default Debug;
|