mbkauthe 4.7.2 → 4.8.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/README.md +3 -3
- package/docs/api.md +12 -12
- package/docs/db.md +3 -1
- package/docs/db.sql +6 -0
- package/docs/env.md +18 -6
- package/index.d.ts +8 -4
- package/lib/config/index.js +8 -5
- package/lib/middleware/auth.js +53 -0
- package/lib/pool.js +12 -8
- package/lib/routes/dbLogs.js +8 -4
- package/lib/routes/oauth.js +361 -355
- package/lib/utils/dbQueryLogger.js +210 -133
- package/package.json +1 -1
- package/test.spec.js +14 -2
- package/views/pages/dbLogs.handlebars +56 -5
- package/views/pages/loginmbkauthe.handlebars +2 -2
|
@@ -1,103 +1,230 @@
|
|
|
1
|
-
import path from "path";
|
|
1
|
+
import path from "path";
|
|
2
2
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3
3
|
|
|
4
4
|
const isDev = process.env.env === "dev" && process.env.dbLogs === "true";
|
|
5
5
|
const requestContext = isDev ? new AsyncLocalStorage() : null;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const GLOBAL_MAX_QUERY_LOG_ENTRIES = 1000;
|
|
8
|
+
const globalQueryState = {
|
|
9
|
+
totalCount: 0,
|
|
10
|
+
log: [],
|
|
10
11
|
};
|
|
12
|
+
let autoPoolId = 0;
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
};
|
|
14
|
+
const safeValue = (value, depth = 0, seen = new WeakSet()) => {
|
|
15
|
+
if (value == null) return value;
|
|
16
|
+
if (typeof value === "string") {
|
|
17
|
+
return value.length > 300 ? `${value.slice(0, 300)}...` : value;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
20
|
+
if (typeof value === "bigint") return value.toString();
|
|
21
|
+
if (value instanceof Date) return value.toISOString();
|
|
22
|
+
if (Buffer.isBuffer(value)) return `[buffer:${value.length}]`;
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
if (depth >= 4) return `[array:${value.length}]`;
|
|
26
|
+
const sample = value.slice(0, 8).map((v) => safeValue(v, depth + 1, seen));
|
|
27
|
+
if (value.length > 8) sample.push(`...(${value.length - 8} more)`);
|
|
28
|
+
return sample;
|
|
29
|
+
}
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
if (typeof value === "object") {
|
|
32
|
+
if (seen.has(value)) return "[circular]";
|
|
33
|
+
seen.add(value);
|
|
34
|
+
|
|
35
|
+
const keys = Object.keys(value);
|
|
36
|
+
if (depth >= 4) {
|
|
37
|
+
const head = keys.slice(0, 5).join(", ");
|
|
38
|
+
return keys.length > 5 ? `[object:${head}, ...]` : `[object:${head}]`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const out = {};
|
|
42
|
+
const entries = Object.entries(value).slice(0, 20);
|
|
43
|
+
for (const [k, v] of entries) {
|
|
44
|
+
out[k] = safeValue(v, depth + 1, seen);
|
|
45
|
+
}
|
|
46
|
+
if (keys.length > 20) {
|
|
47
|
+
out.__truncated = `${keys.length - 20} more keys`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
seen.delete(value);
|
|
51
|
+
return out;
|
|
20
52
|
}
|
|
21
53
|
|
|
22
|
-
|
|
54
|
+
return String(value);
|
|
55
|
+
};
|
|
23
56
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
57
|
+
const toWorkspacePath = (filePath) => {
|
|
58
|
+
const rel = path.relative(process.cwd(), filePath) || filePath;
|
|
59
|
+
return rel.replace(/\\/g, "/");
|
|
60
|
+
};
|
|
28
61
|
|
|
29
|
-
|
|
62
|
+
const buildCallsite = () => {
|
|
63
|
+
try {
|
|
64
|
+
const stack = new Error().stack || "";
|
|
65
|
+
const lines = stack.split("\n").map((l) => l.trim());
|
|
66
|
+
const frame = lines.find(
|
|
67
|
+
(line) =>
|
|
68
|
+
line.startsWith("at ") &&
|
|
69
|
+
!line.includes("/lib/utils/dbQueryLogger.js") &&
|
|
70
|
+
!line.includes("node:internal") &&
|
|
71
|
+
!line.includes("internal/process")
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!frame) return null;
|
|
75
|
+
|
|
76
|
+
const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
|
|
77
|
+
const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
|
|
30
78
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
79
|
+
if (withFunc) {
|
|
80
|
+
return {
|
|
81
|
+
function: withFunc[1],
|
|
82
|
+
file: toWorkspacePath(withFunc[2]),
|
|
83
|
+
line: Number(withFunc[3]),
|
|
84
|
+
column: Number(withFunc[4]),
|
|
85
|
+
};
|
|
35
86
|
}
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (value.length > 8) sample.push(`...(${value.length - 8} more)`);
|
|
45
|
-
return sample;
|
|
87
|
+
|
|
88
|
+
if (noFunc) {
|
|
89
|
+
return {
|
|
90
|
+
function: null,
|
|
91
|
+
file: toWorkspacePath(noFunc[1]),
|
|
92
|
+
line: Number(noFunc[2]),
|
|
93
|
+
column: Number(noFunc[3]),
|
|
94
|
+
};
|
|
46
95
|
}
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
47
99
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
seen.add(value);
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
51
102
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
103
|
+
const buildRequestContext = () => {
|
|
104
|
+
const store = getRequestContext();
|
|
105
|
+
const req = store?.req;
|
|
106
|
+
if (!req) return null;
|
|
107
|
+
|
|
108
|
+
const user = req.session?.user || null;
|
|
109
|
+
return {
|
|
110
|
+
method: req.method,
|
|
111
|
+
url: req.originalUrl || req.url,
|
|
112
|
+
ip: req.ip,
|
|
113
|
+
userId: user?.id || null,
|
|
114
|
+
username: user?.username || null,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
57
117
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for (const [k, v] of entries) {
|
|
61
|
-
out[k] = safeValue(v, depth + 1, seen);
|
|
62
|
-
}
|
|
63
|
-
if (keys.length > 20) {
|
|
64
|
-
out.__truncated = `${keys.length - 20} more keys`;
|
|
65
|
-
}
|
|
118
|
+
const buildReturnValue = (result) => {
|
|
119
|
+
if (!result || typeof result !== "object") return undefined;
|
|
66
120
|
|
|
67
|
-
|
|
68
|
-
|
|
121
|
+
const returnValue = {
|
|
122
|
+
command: result.command || undefined,
|
|
123
|
+
rowCount: typeof result.rowCount === "number" ? result.rowCount : undefined,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (Array.isArray(result.rows)) {
|
|
127
|
+
const previewSize = 3;
|
|
128
|
+
returnValue.returnedRows = result.rows.length;
|
|
129
|
+
returnValue.rowsPreview = result.rows.slice(0, previewSize).map((row) => safeValue(row));
|
|
130
|
+
if (result.rows.length > previewSize) {
|
|
131
|
+
returnValue.rowsTruncated = true;
|
|
69
132
|
}
|
|
133
|
+
}
|
|
70
134
|
|
|
71
|
-
|
|
72
|
-
|
|
135
|
+
return returnValue;
|
|
136
|
+
};
|
|
73
137
|
|
|
74
|
-
|
|
75
|
-
|
|
138
|
+
const recordGlobalLog = (entry) => {
|
|
139
|
+
globalQueryState.totalCount += 1;
|
|
140
|
+
globalQueryState.log.push(entry);
|
|
141
|
+
if (globalQueryState.log.length > GLOBAL_MAX_QUERY_LOG_ENTRIES) {
|
|
142
|
+
globalQueryState.log.shift();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
76
145
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
146
|
+
export const getQueryCount = () => globalQueryState.totalCount;
|
|
147
|
+
export const getQueryLog = (options = {}) => {
|
|
148
|
+
const { limit } = options;
|
|
149
|
+
if (typeof limit === "number") {
|
|
150
|
+
return globalQueryState.log.slice(-limit);
|
|
151
|
+
}
|
|
152
|
+
return [...globalQueryState.log];
|
|
153
|
+
};
|
|
81
154
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
155
|
+
export const resetQueryCount = () => {
|
|
156
|
+
globalQueryState.totalCount = 0;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const resetQueryLog = () => {
|
|
160
|
+
globalQueryState.log.length = 0;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const runWithRequestContext = (req, fn) => {
|
|
164
|
+
if (!isDev || !requestContext) return fn();
|
|
165
|
+
return requestContext.run({ req }, fn);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const getRequestContext = () => {
|
|
169
|
+
if (!isDev || !requestContext) return undefined;
|
|
170
|
+
return requestContext.getStore();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const resolveLoggerPool = (item) => {
|
|
174
|
+
if (item && typeof item === 'object') {
|
|
175
|
+
if (item.pool && item.pool.query) {
|
|
176
|
+
return { pool: item.pool, name: item.name || item.pool.__mbkQueryLoggerName || item.pool.name };
|
|
177
|
+
}
|
|
178
|
+
if (item.query) {
|
|
179
|
+
// Don't force 'default' here; let getPoolName assign a non-default auto name.
|
|
180
|
+
return { pool: item, name: item.__mbkQueryLoggerName || item.name || item.options?.application_name || null };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const getPoolName = (pool, fallbackName) => {
|
|
187
|
+
if (fallbackName) return fallbackName;
|
|
188
|
+
if (pool.__mbkQueryLoggerName) return pool.__mbkQueryLoggerName;
|
|
189
|
+
if (pool.name) return pool.name;
|
|
190
|
+
if (pool.options && pool.options.application_name) return pool.options.application_name;
|
|
191
|
+
// never return "default"; always provide an actual pool name
|
|
192
|
+
autoPoolId += 1;
|
|
193
|
+
return `pool-${autoPoolId}`;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const attachSinglePool = (pool, poolName = null) => {
|
|
197
|
+
if (!pool) return;
|
|
198
|
+
|
|
199
|
+
if (pool.__mbkQueryLoggerInstalled) {
|
|
200
|
+
// Allow late explicit naming to override a previous auto name.
|
|
201
|
+
if (poolName && pool.__mbkQueryLoggerName !== poolName) {
|
|
202
|
+
pool.__mbkQueryLoggerName = poolName;
|
|
89
203
|
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
pool.__mbkQueryLoggerInstalled = true;
|
|
208
|
+
pool.__mbkQueryLoggerName = getPoolName(pool, poolName);
|
|
209
|
+
|
|
210
|
+
let dbQueryCount = 0;
|
|
211
|
+
const dbQueryLog = [];
|
|
212
|
+
const originalQuery = pool.query.bind(pool);
|
|
90
213
|
|
|
91
|
-
|
|
214
|
+
const recordPoolLog = (entry) => {
|
|
215
|
+
dbQueryCount += 1;
|
|
216
|
+
dbQueryLog.push(entry);
|
|
217
|
+
if (dbQueryLog.length > GLOBAL_MAX_QUERY_LOG_ENTRIES) {
|
|
218
|
+
dbQueryLog.shift();
|
|
219
|
+
}
|
|
220
|
+
recordGlobalLog(entry);
|
|
92
221
|
};
|
|
93
222
|
|
|
94
223
|
pool.query = (...args) => {
|
|
95
|
-
dbQueryCount++;
|
|
96
|
-
|
|
97
|
-
// `pg` supports (text, values, callback) or (config, callback).
|
|
98
224
|
let queryText = "";
|
|
99
225
|
let queryName = "";
|
|
100
226
|
let queryValues;
|
|
227
|
+
|
|
101
228
|
try {
|
|
102
229
|
if (typeof args[0] === "string") {
|
|
103
230
|
queryText = args[0];
|
|
@@ -116,66 +243,6 @@ export const attachDevQueryLogger = (pool) => {
|
|
|
116
243
|
}
|
|
117
244
|
|
|
118
245
|
const startTime = process.hrtime.bigint();
|
|
119
|
-
const toWorkspacePath = (filePath) => {
|
|
120
|
-
const rel = path.relative(process.cwd(), filePath) || filePath;
|
|
121
|
-
return rel.replace(/\\/g, "/");
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const buildCallsite = () => {
|
|
125
|
-
try {
|
|
126
|
-
const stack = new Error().stack || "";
|
|
127
|
-
const lines = stack.split("\n").map((l) => l.trim());
|
|
128
|
-
const frame = lines.find(
|
|
129
|
-
(line) =>
|
|
130
|
-
line.startsWith("at ") &&
|
|
131
|
-
!line.includes("/lib/utils/dbQueryLogger.js") &&
|
|
132
|
-
!line.includes("node:internal") &&
|
|
133
|
-
!line.includes("internal/process")
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (!frame) return null;
|
|
137
|
-
|
|
138
|
-
const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
|
|
139
|
-
const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
|
|
140
|
-
|
|
141
|
-
if (withFunc) {
|
|
142
|
-
return {
|
|
143
|
-
function: withFunc[1],
|
|
144
|
-
file: toWorkspacePath(withFunc[2]),
|
|
145
|
-
line: Number(withFunc[3]),
|
|
146
|
-
column: Number(withFunc[4]),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (noFunc) {
|
|
151
|
-
return {
|
|
152
|
-
function: null,
|
|
153
|
-
file: toWorkspacePath(noFunc[1]),
|
|
154
|
-
line: Number(noFunc[2]),
|
|
155
|
-
column: Number(noFunc[3]),
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
} catch {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const buildRequestContext = () => {
|
|
165
|
-
const store = getRequestContext();
|
|
166
|
-
const req = store?.req;
|
|
167
|
-
if (!req) return null;
|
|
168
|
-
|
|
169
|
-
const user = req.session?.user || null;
|
|
170
|
-
return {
|
|
171
|
-
method: req.method,
|
|
172
|
-
url: req.originalUrl || req.url,
|
|
173
|
-
ip: req.ip,
|
|
174
|
-
userId: user?.id || null,
|
|
175
|
-
username: user?.username || null,
|
|
176
|
-
};
|
|
177
|
-
};
|
|
178
|
-
|
|
179
246
|
const callsiteSnapshot = buildCallsite();
|
|
180
247
|
|
|
181
248
|
const recordLog = (success, error, result) => {
|
|
@@ -183,7 +250,7 @@ export const attachDevQueryLogger = (pool) => {
|
|
|
183
250
|
const request = buildRequestContext();
|
|
184
251
|
const returnValue = buildReturnValue(result);
|
|
185
252
|
|
|
186
|
-
|
|
253
|
+
const entry = {
|
|
187
254
|
time: new Date().toISOString(),
|
|
188
255
|
query: queryText,
|
|
189
256
|
name: queryName || undefined,
|
|
@@ -194,16 +261,15 @@ export const attachDevQueryLogger = (pool) => {
|
|
|
194
261
|
returnValue,
|
|
195
262
|
request,
|
|
196
263
|
pool: {
|
|
264
|
+
name: pool.__mbkQueryLoggerName,
|
|
197
265
|
total: pool.totalCount,
|
|
198
266
|
idle: pool.idleCount,
|
|
199
267
|
waiting: pool.waitingCount,
|
|
200
268
|
},
|
|
201
269
|
callsite: callsiteSnapshot,
|
|
202
|
-
}
|
|
270
|
+
};
|
|
203
271
|
|
|
204
|
-
|
|
205
|
-
dbQueryLog.shift();
|
|
206
|
-
}
|
|
272
|
+
recordPoolLog(entry);
|
|
207
273
|
};
|
|
208
274
|
|
|
209
275
|
try {
|
|
@@ -245,3 +311,14 @@ export const attachDevQueryLogger = (pool) => {
|
|
|
245
311
|
dbQueryLog.length = 0;
|
|
246
312
|
};
|
|
247
313
|
};
|
|
314
|
+
|
|
315
|
+
export const attachDevQueryLogger = (poolOrPools) => {
|
|
316
|
+
if (!isDev || !poolOrPools) return;
|
|
317
|
+
|
|
318
|
+
const inputs = Array.isArray(poolOrPools) ? poolOrPools : [poolOrPools];
|
|
319
|
+
for (const item of inputs) {
|
|
320
|
+
const resolved = resolveLoggerPool(item);
|
|
321
|
+
if (!resolved) continue;
|
|
322
|
+
attachSinglePool(resolved.pool, resolved.name);
|
|
323
|
+
}
|
|
324
|
+
};
|
package/package.json
CHANGED
package/test.spec.js
CHANGED
|
@@ -107,10 +107,22 @@ describe('mbkauthe Routes', () => {
|
|
|
107
107
|
const response = await request(BASE_URL).get('/mbkauthe/test');
|
|
108
108
|
expect([200, 302, 401, 403, 429]).toContain(response.status);
|
|
109
109
|
});
|
|
110
|
+
|
|
111
|
+
test('GET /mbkauthe/test with curl UA returns JSON 401', async () => {
|
|
112
|
+
const response = await request(BASE_URL)
|
|
113
|
+
.get('/mbkauthe/test')
|
|
114
|
+
.set('User-Agent', 'curl/8.0.1')
|
|
115
|
+
.set('Accept', '*/*');
|
|
116
|
+
|
|
117
|
+
expect(response.status).toBe(401);
|
|
118
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
119
|
+
expect(response.body).toHaveProperty('success', false);
|
|
120
|
+
expect(response.body).toHaveProperty('errorCode');
|
|
121
|
+
});
|
|
110
122
|
});
|
|
111
123
|
|
|
112
124
|
describe('OAuth Routes', () => {
|
|
113
|
-
test('GET /mbkauthe/api/github/login handles GitHub
|
|
125
|
+
test('GET /mbkauthe/api/github/login handles GitHub App flow', async () => {
|
|
114
126
|
const response = await request(BASE_URL)
|
|
115
127
|
.get('/mbkauthe/api/github/login')
|
|
116
128
|
.redirects(0);
|
|
@@ -123,7 +135,7 @@ describe('mbkauthe Routes', () => {
|
|
|
123
135
|
}
|
|
124
136
|
});
|
|
125
137
|
|
|
126
|
-
test('GET /mbkauthe/api/github/login/callback handles callback', async () => {
|
|
138
|
+
test('GET /mbkauthe/api/github/login/callback handles GitHub App callback', async () => {
|
|
127
139
|
const response = await request(BASE_URL).get('/mbkauthe/api/github/login/callback');
|
|
128
140
|
expect([200, 302, 400, 401, 403, 429]).toContain(response.status);
|
|
129
141
|
});
|
|
@@ -183,6 +183,16 @@
|
|
|
183
183
|
padding: 0.1rem 0.45rem;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
.log-pool {
|
|
187
|
+
font-size: 0.75rem;
|
|
188
|
+
color: var(--text-light);
|
|
189
|
+
border: 1px solid var(--muted-border);
|
|
190
|
+
border-radius: 999px;
|
|
191
|
+
padding: 0.1rem 0.45rem;
|
|
192
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent 88%);
|
|
193
|
+
margin: 0 0.25rem;
|
|
194
|
+
}
|
|
195
|
+
|
|
186
196
|
.log-time {
|
|
187
197
|
color: var(--text-light);
|
|
188
198
|
font-size: 0.8rem;
|
|
@@ -402,6 +412,10 @@
|
|
|
402
412
|
<input id="limit" name="limit" type="number" min="1" max="500" value="{{queryLimit}}" {{#unless isDev}}disabled{{/unless}} />
|
|
403
413
|
<button type="submit" {{#unless isDev}}disabled{{/unless}}>Update</button>
|
|
404
414
|
</form>
|
|
415
|
+
<label for="pool-filter">Pool</label>
|
|
416
|
+
<select id="pool-filter" {{#unless isDev}}disabled{{/unless}}>
|
|
417
|
+
<option value="">All Pools</option>
|
|
418
|
+
</select>
|
|
405
419
|
<button id="reset-btn" type="button" {{#unless isDev}}disabled{{/unless}}>Reset</button>
|
|
406
420
|
<button id="copy-btn" type="button" {{#unless isDev}}disabled{{/unless}}>Copy Logs</button>
|
|
407
421
|
</div>
|
|
@@ -420,6 +434,7 @@
|
|
|
420
434
|
|
|
421
435
|
<script>
|
|
422
436
|
const limitInput = document.getElementById('limit');
|
|
437
|
+
const poolFilterEl = document.getElementById('pool-filter');
|
|
423
438
|
const queryCountEl = document.getElementById('query-count');
|
|
424
439
|
const queryLimitEl = document.getElementById('query-limit');
|
|
425
440
|
const queryLogEl = document.getElementById('query-log');
|
|
@@ -428,6 +443,7 @@
|
|
|
428
443
|
const resetNoticeEl = document.getElementById('reset-notice');
|
|
429
444
|
const isDev = {{#if isDev}}true{{else}}false{{/if}};
|
|
430
445
|
let latestQueryLog = [];
|
|
446
|
+
let currentPoolFilter = '';
|
|
431
447
|
|
|
432
448
|
const showDisabledState = (message = 'DB logs are disabled.') => {
|
|
433
449
|
queryCountEl.textContent = '-';
|
|
@@ -443,6 +459,35 @@
|
|
|
443
459
|
.replace(/\|/g, '\\|')
|
|
444
460
|
.replace(/\r?\n/g, '<br>');
|
|
445
461
|
|
|
462
|
+
const getUniquePoolNames = (log) => {
|
|
463
|
+
if (!Array.isArray(log)) return [];
|
|
464
|
+
const names = new Set();
|
|
465
|
+
for (const entry of log) {
|
|
466
|
+
const rawPoolName = entry?.pool?.name || 'default';
|
|
467
|
+
if (rawPoolName && rawPoolName !== 'default') {
|
|
468
|
+
names.add(rawPoolName);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return [...names].sort();
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const renderPoolFilterOptions = (names) => {
|
|
475
|
+
if (!poolFilterEl) return;
|
|
476
|
+
const selected = poolFilterEl.value || '';
|
|
477
|
+
poolFilterEl.innerHTML = '<option value="">All Pools</option>' +
|
|
478
|
+
names.map((name) => `\n <option value="${escapeHtml(name)}"${name === selected ? ' selected' : ''}>${escapeHtml(name)}</option>`).join('');
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const getFilteredLog = (log) => {
|
|
482
|
+
if (!Array.isArray(log) || !currentPoolFilter) return log;
|
|
483
|
+
return log.filter((entry) => (entry?.pool?.name || 'default') === currentPoolFilter);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
poolFilterEl?.addEventListener('change', () => {
|
|
487
|
+
currentPoolFilter = poolFilterEl.value || '';
|
|
488
|
+
buildTable(getFilteredLog(latestQueryLog));
|
|
489
|
+
});
|
|
490
|
+
|
|
446
491
|
const formatParamValues = (values, pretty = false) => {
|
|
447
492
|
if (!Array.isArray(values) || values.length === 0) return '';
|
|
448
493
|
|
|
@@ -495,8 +540,10 @@
|
|
|
495
540
|
`\nuser: ${entry.request.username || entry.request.userId || 'anonymous'}` +
|
|
496
541
|
`\nip: ${entry.request.ip || ''}`
|
|
497
542
|
: '';
|
|
498
|
-
const
|
|
499
|
-
|
|
543
|
+
const rawPoolName = entry.pool?.name || 'default';
|
|
544
|
+
const poolName = rawPoolName === 'default' ? '' : rawPoolName;
|
|
545
|
+
const poolText = entry.pool && poolName
|
|
546
|
+
? `name:${poolName} total:${entry.pool.total} idle:${entry.pool.idle} waiting:${entry.pool.waiting}`
|
|
500
547
|
: '';
|
|
501
548
|
const callsiteText = entry.callsite
|
|
502
549
|
? `${entry.callsite.function || '(anonymous)'}\n${entry.callsite.file}:${entry.callsite.line}:${entry.callsite.column}`
|
|
@@ -545,8 +592,10 @@
|
|
|
545
592
|
`\nuser: ${entry.request.username || entry.request.userId || 'anonymous'}` +
|
|
546
593
|
`\nip: ${entry.request.ip || ''}`
|
|
547
594
|
: '';
|
|
548
|
-
const
|
|
549
|
-
|
|
595
|
+
const rawPoolName = entry.pool?.name || 'default';
|
|
596
|
+
const poolName = rawPoolName === 'default' ? '' : rawPoolName;
|
|
597
|
+
const poolText = entry.pool && poolName
|
|
598
|
+
? `name:${poolName} total:${entry.pool.total} idle:${entry.pool.idle} waiting:${entry.pool.waiting}`
|
|
550
599
|
: '';
|
|
551
600
|
|
|
552
601
|
const detailBlocks = [
|
|
@@ -563,6 +612,7 @@
|
|
|
563
612
|
<div class="log-mainline">
|
|
564
613
|
<span class="log-index">#${idx + 1}</span>
|
|
565
614
|
${statusBadge}
|
|
615
|
+
${poolName ? `<span class="log-pool">${escapeHtml(poolName)}</span>` : ''}
|
|
566
616
|
<span class="log-time">${escapeHtml(entry.time)}</span>
|
|
567
617
|
</div>
|
|
568
618
|
<button class="copy-row-btn" data-copy='${escapeHtml(JSON.stringify(entry))}'>Copy</button>
|
|
@@ -625,7 +675,8 @@
|
|
|
625
675
|
|
|
626
676
|
queryCountEl.textContent = data.queryCount ?? '-';
|
|
627
677
|
latestQueryLog = Array.isArray(data.queryLog) ? data.queryLog : [];
|
|
628
|
-
|
|
678
|
+
renderPoolFilterOptions(getUniquePoolNames(latestQueryLog));
|
|
679
|
+
buildTable(getFilteredLog(latestQueryLog));
|
|
629
680
|
};
|
|
630
681
|
|
|
631
682
|
resetBtn.addEventListener('click', () => {
|
|
@@ -396,12 +396,12 @@
|
|
|
396
396
|
|
|
397
397
|
{{#if githubLoginEnabled }}
|
|
398
398
|
|
|
399
|
-
// GitHub login: Navigate directly to GitHub
|
|
399
|
+
// GitHub login: Navigate directly to GitHub App flow
|
|
400
400
|
async function startGithubLogin() {
|
|
401
401
|
const urlParams = new URLSearchParams(window.location.search);
|
|
402
402
|
const redirect = urlParams.get('redirect') || '{{customURL}}';
|
|
403
403
|
|
|
404
|
-
// Navigate directly to the backend
|
|
404
|
+
// Navigate directly to the backend GitHub App endpoint with redirect query
|
|
405
405
|
window.location.href = `/mbkauthe/api/github/login?redirect=${encodeURIComponent(redirect)}`;
|
|
406
406
|
}
|
|
407
407
|
|