web_plsql 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -8
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/frontend/assets/main-C8GKnEhz.js +147 -0
- package/dist/frontend/assets/main-C8GKnEhz.js.br +0 -0
- package/dist/frontend/assets/main-C8GKnEhz.js.gz +0 -0
- package/dist/frontend/assets/main-bU4x_yEW.css +1 -0
- package/dist/frontend/assets/main-bU4x_yEW.css.br +0 -0
- package/dist/frontend/assets/main-bU4x_yEW.css.gz +0 -0
- package/{src/admin → dist/frontend}/index.html +12 -25
- package/dist/frontend/index.html.br +0 -0
- package/dist/frontend/index.html.gz +0 -0
- package/dist/index.d.mts +646 -0
- package/dist/index.mjs +3559 -0
- package/dist/index.mjs.map +1 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
- package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
- package/examples/config-apex.js +1 -1
- package/examples/config-docker.js +1 -2
- package/package.json +48 -32
- package/examples/config-native.js +0 -68
- package/src/admin/client/charts.ts +0 -598
- package/src/admin/client/main.js +0 -3
- package/src/admin/client/tailwind.css +0 -41
- package/src/admin/js/api.ts +0 -170
- package/src/admin/js/app.ts +0 -614
- package/src/admin/js/eslint.config.js +0 -74
- package/src/admin/js/schemas.ts +0 -215
- package/src/admin/js/templates/config.ts +0 -147
- package/src/admin/js/templates/errorRow.ts +0 -21
- package/src/admin/js/templates/index.ts +0 -4
- package/src/admin/js/templates/poolCard.ts +0 -61
- package/src/admin/js/templates/traceRow.ts +0 -24
- package/src/admin/js/tsconfig.json +0 -24
- package/src/admin/js/types.ts +0 -336
- package/src/admin/js/ui/components.ts +0 -44
- package/src/admin/js/ui/table.ts +0 -173
- package/src/admin/js/ui/theme.ts +0 -93
- package/src/admin/js/ui/views.ts +0 -427
- package/src/admin/js/util/format.ts +0 -46
- package/src/admin/js/util/metrics.ts +0 -24
- package/src/admin/lib/chart.bundle.css +0 -1
- package/src/admin/lib/chart.bundle.js +0 -146
- package/src/admin/style.css +0 -1437
- package/src/bin/load-test.js +0 -202
- package/src/handler/handlerAdmin.js +0 -293
- package/src/handler/handlerLogger.js +0 -21
- package/src/handler/handlerUpload.js +0 -26
- package/src/handler/plsql/cgi.js +0 -131
- package/src/handler/plsql/errorPage.js +0 -113
- package/src/handler/plsql/handlerPlSql.js +0 -90
- package/src/handler/plsql/owaPageStream.js +0 -93
- package/src/handler/plsql/parsePage.js +0 -213
- package/src/handler/plsql/procedure.js +0 -426
- package/src/handler/plsql/procedureError.js +0 -40
- package/src/handler/plsql/procedureNamed.js +0 -282
- package/src/handler/plsql/procedureSanitize.js +0 -286
- package/src/handler/plsql/procedureVariable.js +0 -56
- package/src/handler/plsql/request.js +0 -124
- package/src/handler/plsql/requestError.js +0 -23
- package/src/handler/plsql/sendResponse.js +0 -174
- package/src/handler/plsql/stream.js +0 -28
- package/src/handler/plsql/upload.js +0 -109
- package/src/index.js +0 -19
- package/src/server/adminContext.js +0 -23
- package/src/server/config.js +0 -57
- package/src/server/connections.js +0 -40
- package/src/server/server.js +0 -304
- package/src/types.js +0 -150
- package/src/util/cache.js +0 -123
- package/src/util/errorToString.js +0 -24
- package/src/util/file.js +0 -91
- package/src/util/html.js +0 -53
- package/src/util/jsonLogger.js +0 -47
- package/src/util/oracle.js +0 -80
- package/src/util/shutdown.js +0 -44
- package/src/util/statsManager.js +0 -368
- package/src/util/trace.js +0 -326
- package/src/util/traceManager.js +0 -68
- package/src/util/type.js +0 -6
- package/src/util/util.js +0 -66
- package/src/version.js +0 -5
- package/types/admin/js/schemas.d.ts +0 -384
- package/types/admin/js/types.d.ts +0 -312
- package/types/handler/handlerAdmin.d.ts +0 -79
- package/types/handler/handlerLogger.d.ts +0 -2
- package/types/handler/handlerUpload.d.ts +0 -7
- package/types/handler/plsql/cgi.d.ts +0 -4
- package/types/handler/plsql/errorPage.d.ts +0 -11
- package/types/handler/plsql/handlerPlSql.d.ts +0 -13
- package/types/handler/plsql/owaPageStream.d.ts +0 -28
- package/types/handler/plsql/parsePage.d.ts +0 -3
- package/types/handler/plsql/procedure.d.ts +0 -13
- package/types/handler/plsql/procedureError.d.ts +0 -23
- package/types/handler/plsql/procedureNamed.d.ts +0 -14
- package/types/handler/plsql/procedureSanitize.d.ts +0 -11
- package/types/handler/plsql/procedureVariable.d.ts +0 -10
- package/types/handler/plsql/request.d.ts +0 -9
- package/types/handler/plsql/requestError.d.ts +0 -8
- package/types/handler/plsql/sendResponse.d.ts +0 -6
- package/types/handler/plsql/upload.d.ts +0 -5
- package/types/index.d.ts +0 -10
- package/types/server/adminContext.d.ts +0 -17
- package/types/server/config.d.ts +0 -2
- package/types/server/server.d.ts +0 -57
- package/types/types.d.ts +0 -328
- package/types/util/cache.d.ts +0 -69
- package/types/util/errorToString.d.ts +0 -1
- package/types/util/file.d.ts +0 -6
- package/types/util/html.d.ts +0 -3
- package/types/util/jsonLogger.d.ts +0 -45
- package/types/util/oracle.d.ts +0 -5
- package/types/util/shutdown.d.ts +0 -2
- package/types/util/statsManager.d.ts +0 -395
- package/types/util/trace.d.ts +0 -22
- package/types/util/traceManager.d.ts +0 -35
- package/types/util/type.d.ts +0 -1
- package/types/util/util.d.ts +0 -3
- package/types/version.d.ts +0 -1
- /package/{src/admin/favicon.svg → dist/frontend/assets/favicon-mQAM4tVu.svg} +0 -0
package/src/bin/load-test.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Load testing CLI tool for web_plsql.
|
|
3
|
-
* This tool simulates parallel traffic to the middleware and provides real-time statistics.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {performance} from 'node:perf_hooks';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {object} Metrics
|
|
10
|
-
* @property {number} total Total requests made.
|
|
11
|
-
* @property {number} success Number of successful requests (2xx).
|
|
12
|
-
* @property {number} error Number of failed requests.
|
|
13
|
-
* @property {number} minLatency Minimum latency in milliseconds.
|
|
14
|
-
* @property {number} maxLatency Maximum latency in milliseconds.
|
|
15
|
-
* @property {number} sumLatency Sum of all latencies for average calculation.
|
|
16
|
-
* @property {Map<string|number, number>} statusCodes Frequency of status codes.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** @type {Metrics} */
|
|
20
|
-
const metrics = {
|
|
21
|
-
total: 0,
|
|
22
|
-
success: 0,
|
|
23
|
-
error: 0,
|
|
24
|
-
minLatency: Number.MAX_SAFE_INTEGER,
|
|
25
|
-
maxLatency: 0,
|
|
26
|
-
sumLatency: 0,
|
|
27
|
-
statusCodes: new Map(),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
let stopped = false;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parse CLI arguments.
|
|
34
|
-
* @returns {{ url: string, concurrency: number, duration: number }} Options object.
|
|
35
|
-
*/
|
|
36
|
-
function parseArgs() {
|
|
37
|
-
const args = process.argv.slice(2);
|
|
38
|
-
const options = {
|
|
39
|
-
url: '',
|
|
40
|
-
concurrency: 10,
|
|
41
|
-
duration: 30,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < args.length; i++) {
|
|
45
|
-
const arg = args[i];
|
|
46
|
-
const nextArg = args[i + 1] ?? '';
|
|
47
|
-
|
|
48
|
-
if (arg === '--url' || arg === '-u') {
|
|
49
|
-
options.url = nextArg;
|
|
50
|
-
i++;
|
|
51
|
-
} else if (arg === '--concurrency' || arg === '-c') {
|
|
52
|
-
options.concurrency = parseInt(nextArg, 10);
|
|
53
|
-
i++;
|
|
54
|
-
} else if (arg === '--duration' || arg === '-d') {
|
|
55
|
-
options.duration = parseInt(nextArg, 10);
|
|
56
|
-
i++;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!options.url) {
|
|
61
|
-
console.error('Usage: node src/bin/load-test.js --url <url> [-c concurrency] [-d duration]');
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return options;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Record a single request's result.
|
|
70
|
-
* @param {boolean} ok Whether the request succeeded.
|
|
71
|
-
* @param {number} latency Latency in milliseconds.
|
|
72
|
-
* @param {number|string} status Status code or error string.
|
|
73
|
-
*/
|
|
74
|
-
function recordMetrics(ok, latency, status) {
|
|
75
|
-
metrics.total++;
|
|
76
|
-
if (ok) {
|
|
77
|
-
metrics.success++;
|
|
78
|
-
} else {
|
|
79
|
-
metrics.error++;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
metrics.minLatency = Math.min(metrics.minLatency, latency);
|
|
83
|
-
metrics.maxLatency = Math.max(metrics.maxLatency, latency);
|
|
84
|
-
metrics.sumLatency += latency;
|
|
85
|
-
|
|
86
|
-
const count = metrics.statusCodes.get(status) ?? 0;
|
|
87
|
-
metrics.statusCodes.set(status, count + 1);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Clear the console and move cursor to the top.
|
|
92
|
-
*/
|
|
93
|
-
function clearConsole() {
|
|
94
|
-
process.stdout.write('\x1b[2J\x1b[0;0H');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Render the dashboard.
|
|
99
|
-
* @param {number} elapsed Elapsed time in seconds.
|
|
100
|
-
* @param {number} duration Total duration in seconds.
|
|
101
|
-
* @param {string} url Target URL.
|
|
102
|
-
* @param {number} concurrency Concurrency level.
|
|
103
|
-
*/
|
|
104
|
-
function renderDashboard(elapsed, duration, url, concurrency) {
|
|
105
|
-
clearConsole();
|
|
106
|
-
const rps = elapsed > 0 ? (metrics.total / elapsed).toFixed(1) : '0.0';
|
|
107
|
-
const avgLatency = metrics.total > 0 ? (metrics.sumLatency / metrics.total).toFixed(1) : '0.0';
|
|
108
|
-
const progress = Math.min(100, (elapsed / duration) * 100).toFixed(1);
|
|
109
|
-
const barWidth = 30;
|
|
110
|
-
const filledWidth = Math.floor((barWidth * parseFloat(progress)) / 100);
|
|
111
|
-
const bar = '='.repeat(filledWidth) + '>'.padEnd(barWidth - filledWidth, ' ');
|
|
112
|
-
|
|
113
|
-
console.log('\x1b[1m\x1b[36mWeb PL/SQL Load Test\x1b[0m');
|
|
114
|
-
console.log('='.repeat(40));
|
|
115
|
-
console.log(`\x1b[33mURL:\x1b[0m ${url}`);
|
|
116
|
-
console.log(`\x1b[33mConcurrency:\x1b[0m ${concurrency}`);
|
|
117
|
-
console.log(`\x1b[33mDuration:\x1b[0m ${duration}s`);
|
|
118
|
-
console.log('='.repeat(40));
|
|
119
|
-
console.log(`\x1b[32mProgress:\x1b[0m [${bar}] ${progress}% (${elapsed.toFixed(1)}s)`);
|
|
120
|
-
console.log(`\x1b[32mRPS:\x1b[0m ${rps}`);
|
|
121
|
-
console.log(`\x1b[32mTotal:\x1b[0m ${metrics.total}`);
|
|
122
|
-
console.log(`\x1b[32mSuccess:\x1b[0m ${metrics.success}`);
|
|
123
|
-
console.log(`\x1b[31mErrors:\x1b[0m ${metrics.error}`);
|
|
124
|
-
console.log('='.repeat(40));
|
|
125
|
-
console.log('\x1b[1mLatency (ms)\x1b[0m');
|
|
126
|
-
console.log(`\x1b[34mAvg:\x1b[0m ${avgLatency.padStart(8)}`);
|
|
127
|
-
console.log(`\x1b[34mMin:\x1b[0m ${(metrics.minLatency === Number.MAX_SAFE_INTEGER ? 0 : metrics.minLatency).toFixed(1).padStart(8)}`);
|
|
128
|
-
console.log(`\x1b[34mMax:\x1b[0m ${metrics.maxLatency.toFixed(1).padStart(8)}`);
|
|
129
|
-
console.log('='.repeat(40));
|
|
130
|
-
|
|
131
|
-
if (metrics.statusCodes.size > 0) {
|
|
132
|
-
console.log('\x1b[1mStatus Codes\x1b[0m');
|
|
133
|
-
for (const [code, count] of metrics.statusCodes.entries()) {
|
|
134
|
-
const numericCode = typeof code === 'number' ? code : 0;
|
|
135
|
-
const color = numericCode >= 200 && numericCode < 300 ? '\x1b[32m' : '\x1b[31m';
|
|
136
|
-
console.log(`${color}${code}\x1b[0m: ${count}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Worker function to maintain concurrency.
|
|
143
|
-
* @param {string} url The target URL.
|
|
144
|
-
* @param {number} endTime The end time in milliseconds.
|
|
145
|
-
*/
|
|
146
|
-
async function runWorker(url, endTime) {
|
|
147
|
-
while (performance.now() < endTime && !stopped) {
|
|
148
|
-
const start = performance.now();
|
|
149
|
-
try {
|
|
150
|
-
const res = await fetch(url, {
|
|
151
|
-
signal: AbortSignal.timeout(10000), // 10s timeout
|
|
152
|
-
});
|
|
153
|
-
const latency = performance.now() - start;
|
|
154
|
-
recordMetrics(res.ok, latency, res.status);
|
|
155
|
-
// Consume body to free up connection
|
|
156
|
-
await res.arrayBuffer();
|
|
157
|
-
} catch (err) {
|
|
158
|
-
const latency = performance.now() - start;
|
|
159
|
-
const errorName = err instanceof Error ? err.name : 'UnknownError';
|
|
160
|
-
recordMetrics(false, latency, errorName === 'TimeoutError' ? 'Timeout' : 'NetworkErr');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Main function.
|
|
167
|
-
*/
|
|
168
|
-
async function main() {
|
|
169
|
-
const options = parseArgs();
|
|
170
|
-
const startTime = performance.now();
|
|
171
|
-
const endTime = startTime + options.duration * 1000;
|
|
172
|
-
|
|
173
|
-
console.log(`Starting load test on ${options.url}...`);
|
|
174
|
-
|
|
175
|
-
// Start workers
|
|
176
|
-
const workers = Array.from({length: options.concurrency}, () => runWorker(options.url, endTime));
|
|
177
|
-
|
|
178
|
-
// Dashboard refresh interval
|
|
179
|
-
const uiInterval = setInterval(() => {
|
|
180
|
-
const elapsed = (performance.now() - startTime) / 1000;
|
|
181
|
-
renderDashboard(elapsed, options.duration, options.url, options.concurrency);
|
|
182
|
-
}, 200);
|
|
183
|
-
|
|
184
|
-
// Handle Ctrl+C
|
|
185
|
-
process.on('SIGINT', () => {
|
|
186
|
-
stopped = true;
|
|
187
|
-
console.log('\nStopping...');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
await Promise.all(workers);
|
|
191
|
-
clearInterval(uiInterval);
|
|
192
|
-
|
|
193
|
-
// Final render
|
|
194
|
-
const finalElapsed = (performance.now() - startTime) / 1000;
|
|
195
|
-
renderDashboard(finalElapsed, options.duration, options.url, options.concurrency);
|
|
196
|
-
console.log('\n\x1b[1m\x1b[32mTest Complete!\x1b[0m\n');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
main().catch((/** @type {unknown} */ err) => {
|
|
200
|
-
console.error('Fatal error:', err);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
});
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import readline from 'node:readline';
|
|
5
|
-
import {AdminContext} from '../server/adminContext.js';
|
|
6
|
-
import {traceManager} from '../util/traceManager.js';
|
|
7
|
-
import {getVersion} from '../version.js';
|
|
8
|
-
|
|
9
|
-
const version = getVersion();
|
|
10
|
-
import {forceShutdown} from '../util/shutdown.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {import('express').Request} Request
|
|
14
|
-
* @typedef {import('express').Response} Response
|
|
15
|
-
* @typedef {import('express').NextFunction} NextFunction
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {import('../util/statsManager.js').Bucket} Bucket
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {object} StatsSummary
|
|
24
|
-
* @property {Date} startTime - Server start time.
|
|
25
|
-
* @property {number} totalRequests - Total requests handled.
|
|
26
|
-
* @property {number} totalErrors - Total errors encountered.
|
|
27
|
-
* @property {number} avgResponseTime - Lifetime average response time.
|
|
28
|
-
* @property {number} minResponseTime - Lifetime minimum response time.
|
|
29
|
-
* @property {number} maxResponseTime - Lifetime maximum response time.
|
|
30
|
-
* @property {number} maxRequestsPerSecond - Lifetime maximum requests per second.
|
|
31
|
-
* @property {object} maxMemory - Lifetime memory extremes.
|
|
32
|
-
* @property {number} maxMemory.heapUsedMax - Maximum heap used.
|
|
33
|
-
* @property {number} maxMemory.heapTotalMax - Maximum heap total.
|
|
34
|
-
* @property {number} maxMemory.rssMax - Maximum RSS.
|
|
35
|
-
* @property {number} maxMemory.externalMax - Maximum external memory.
|
|
36
|
-
* @property {object} cpu - Lifetime CPU extremes.
|
|
37
|
-
* @property {number} cpu.max - Max CPU.
|
|
38
|
-
* @property {number} cpu.userMax - Max user CPU.
|
|
39
|
-
* @property {number} cpu.systemMax - Max system CPU.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
export const handlerAdmin = express.Router();
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Helper to read last N lines of a file
|
|
46
|
-
* @param {string} filePath - Path to file
|
|
47
|
-
* @param {number} n - Number of lines
|
|
48
|
-
* @param {string} [filter] - Optional filter string
|
|
49
|
-
* @returns {Promise<string[]>} - The lines
|
|
50
|
-
*/
|
|
51
|
-
const readLastLines = async (filePath, n = 100, filter = '') => {
|
|
52
|
-
if (!fs.existsSync(filePath)) {
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const fileStream = fs.createReadStream(filePath);
|
|
57
|
-
const rl = readline.createInterface({
|
|
58
|
-
input: fileStream,
|
|
59
|
-
crlfDelay: Infinity,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const filterLower = filter.toLowerCase();
|
|
63
|
-
|
|
64
|
-
/** @type {string[]} */
|
|
65
|
-
const lines = [];
|
|
66
|
-
for await (const line of rl) {
|
|
67
|
-
if (!filter || line.toLowerCase().includes(filterLower)) {
|
|
68
|
-
lines.push(line);
|
|
69
|
-
if (lines.length > n) {
|
|
70
|
-
lines.shift();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return lines;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// GET /api/status
|
|
78
|
-
handlerAdmin.get('/api/status', (_req, res) => {
|
|
79
|
-
const uptime = (new Date().getTime() - AdminContext.startTime.getTime()) / 1000;
|
|
80
|
-
|
|
81
|
-
const poolStats = AdminContext.pools.map((pool, index) => {
|
|
82
|
-
const cache = AdminContext.caches[index];
|
|
83
|
-
const name = cache?.poolName ?? `pool-${index}`;
|
|
84
|
-
const p = /** @type {import('oracledb').Pool & {getStatistics?: () => Record<string, unknown>}} */ (pool);
|
|
85
|
-
const procStats = cache?.procedureNameCache.getStats();
|
|
86
|
-
const argStats = cache?.argumentCache.getStats();
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
name,
|
|
90
|
-
stats: typeof p.getStatistics === 'function' ? p.getStatistics() : null,
|
|
91
|
-
connectionsOpen: pool.connectionsOpen,
|
|
92
|
-
connectionsInUse: pool.connectionsInUse,
|
|
93
|
-
cache: {
|
|
94
|
-
procedureName: {
|
|
95
|
-
size: cache?.procedureNameCache.keys().length ?? 0,
|
|
96
|
-
hits: procStats?.hits ?? 0,
|
|
97
|
-
misses: procStats?.misses ?? 0,
|
|
98
|
-
},
|
|
99
|
-
argument: {
|
|
100
|
-
size: cache?.argumentCache.keys().length ?? 0,
|
|
101
|
-
hits: argStats?.hits ?? 0,
|
|
102
|
-
misses: argStats?.misses ?? 0,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const memUsage = process.memoryUsage();
|
|
109
|
-
const systemMemoryUsed = os.totalmem() - os.freemem();
|
|
110
|
-
const cpuUsage = process.cpuUsage();
|
|
111
|
-
const summary = /** @type {StatsSummary} */ (AdminContext.statsManager.getSummary());
|
|
112
|
-
|
|
113
|
-
res.json({
|
|
114
|
-
version,
|
|
115
|
-
status: AdminContext.paused ? 'paused' : 'running',
|
|
116
|
-
uptime,
|
|
117
|
-
startTime: AdminContext.startTime,
|
|
118
|
-
intervalMs: AdminContext.statsManager.config.intervalMs,
|
|
119
|
-
metrics: {
|
|
120
|
-
requestCount: summary.totalRequests,
|
|
121
|
-
errorCount: summary.totalErrors,
|
|
122
|
-
avgResponseTime: summary.avgResponseTime,
|
|
123
|
-
minResponseTime: summary.minResponseTime,
|
|
124
|
-
maxResponseTime: summary.maxResponseTime,
|
|
125
|
-
maxRequestsPerSecond: summary.maxRequestsPerSecond,
|
|
126
|
-
},
|
|
127
|
-
history: AdminContext.statsManager.getHistory(),
|
|
128
|
-
pools: poolStats,
|
|
129
|
-
system: {
|
|
130
|
-
nodeVersion: process.version,
|
|
131
|
-
platform: process.platform,
|
|
132
|
-
arch: process.arch,
|
|
133
|
-
cpuCores: os.cpus().length,
|
|
134
|
-
memory: {
|
|
135
|
-
rss: systemMemoryUsed,
|
|
136
|
-
heapTotal: memUsage.heapTotal,
|
|
137
|
-
heapUsed: memUsage.heapUsed,
|
|
138
|
-
external: memUsage.external,
|
|
139
|
-
totalMemory: os.totalmem(),
|
|
140
|
-
...summary.maxMemory,
|
|
141
|
-
},
|
|
142
|
-
cpu: {
|
|
143
|
-
user: cpuUsage.user,
|
|
144
|
-
system: cpuUsage.system,
|
|
145
|
-
max: summary.cpu.max,
|
|
146
|
-
userMax: summary.cpu.userMax,
|
|
147
|
-
systemMax: summary.cpu.systemMax,
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
config: AdminContext.config
|
|
151
|
-
? {
|
|
152
|
-
...AdminContext.config,
|
|
153
|
-
adminPassword: AdminContext.config.adminPassword ? '********' : undefined,
|
|
154
|
-
routePlSql: AdminContext.config.routePlSql.map((p) => ({
|
|
155
|
-
...p,
|
|
156
|
-
password: '********',
|
|
157
|
-
})),
|
|
158
|
-
}
|
|
159
|
-
: null,
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// GET /api/logs/error
|
|
164
|
-
handlerAdmin.get('/api/logs/error', async (req, res) => {
|
|
165
|
-
try {
|
|
166
|
-
const limit = Number(req.query.limit) || 100;
|
|
167
|
-
const filter = typeof req.query.filter === 'string' ? req.query.filter : '';
|
|
168
|
-
const logFile = 'error.json.log';
|
|
169
|
-
const lines = await readLastLines(logFile, limit, filter);
|
|
170
|
-
|
|
171
|
-
/** @type {Record<string, unknown>[]} */
|
|
172
|
-
const logs = [];
|
|
173
|
-
for (const line of lines) {
|
|
174
|
-
try {
|
|
175
|
-
const parsed = /** @type {unknown} */ (JSON.parse(line));
|
|
176
|
-
if (parsed && typeof parsed === 'object') {
|
|
177
|
-
logs.push(/** @type {Record<string, unknown>} */ (parsed));
|
|
178
|
-
}
|
|
179
|
-
} catch (e) {
|
|
180
|
-
// ignore
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
res.json(logs.reverse());
|
|
185
|
-
} catch (err) {
|
|
186
|
-
res.status(500).json({error: String(err)});
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// GET /api/logs/access
|
|
191
|
-
handlerAdmin.get('/api/logs/access', async (req, res) => {
|
|
192
|
-
try {
|
|
193
|
-
const limit = Number(req.query.limit) || 100;
|
|
194
|
-
const filter = typeof req.query.filter === 'string' ? req.query.filter : '';
|
|
195
|
-
const logFile = AdminContext.config?.loggerFilename ?? 'access.log';
|
|
196
|
-
|
|
197
|
-
if (!AdminContext.config?.loggerFilename) {
|
|
198
|
-
res.json({message: 'Access logging not enabled'});
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const lines = await readLastLines(logFile, limit, filter);
|
|
203
|
-
res.json(lines.reverse());
|
|
204
|
-
} catch (err) {
|
|
205
|
-
res.status(500).json({error: String(err)});
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// GET /api/cache - REMOVED (Merged into /api/status)
|
|
210
|
-
|
|
211
|
-
// POST /api/cache/clear
|
|
212
|
-
handlerAdmin.post('/api/cache/clear', (req, res) => {
|
|
213
|
-
const body = /** @type {unknown} */ (req.body);
|
|
214
|
-
const poolName = body && typeof body === 'object' && 'poolName' in body && typeof body.poolName === 'string' ? body.poolName : undefined;
|
|
215
|
-
|
|
216
|
-
let cleared = 0;
|
|
217
|
-
AdminContext.caches.forEach((c) => {
|
|
218
|
-
if (!poolName || c.poolName === poolName) {
|
|
219
|
-
c.procedureNameCache.clear();
|
|
220
|
-
cleared++;
|
|
221
|
-
c.argumentCache.clear();
|
|
222
|
-
cleared++;
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
res.json({message: `Cleared ${cleared} caches`});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// POST /api/server/:action
|
|
230
|
-
handlerAdmin.post('/api/server/:action', (req, res) => {
|
|
231
|
-
const action = req.params.action;
|
|
232
|
-
|
|
233
|
-
if (action === 'stop') {
|
|
234
|
-
res.json({message: 'Server shutting down...'});
|
|
235
|
-
setTimeout(() => {
|
|
236
|
-
forceShutdown();
|
|
237
|
-
}, 100);
|
|
238
|
-
} else if (action === 'pause') {
|
|
239
|
-
AdminContext.paused = true;
|
|
240
|
-
res.json({message: 'Server paused', status: 'paused'});
|
|
241
|
-
} else if (action === 'resume') {
|
|
242
|
-
AdminContext.paused = false;
|
|
243
|
-
res.json({message: 'Server resumed', status: 'running'});
|
|
244
|
-
} else {
|
|
245
|
-
res.status(400).json({error: 'Invalid action'});
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// GET /api/trace/status
|
|
250
|
-
handlerAdmin.get('/api/trace/status', (_req, res) => {
|
|
251
|
-
res.json({enabled: traceManager.isEnabled()});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// POST /api/trace/toggle
|
|
255
|
-
handlerAdmin.post('/api/trace/toggle', (req, res) => {
|
|
256
|
-
const body = /** @type {unknown} */ (req.body);
|
|
257
|
-
const enabled = body && typeof body === 'object' && 'enabled' in body && typeof body.enabled === 'boolean' ? body.enabled : false;
|
|
258
|
-
traceManager.setEnabled(enabled);
|
|
259
|
-
res.json({enabled: traceManager.isEnabled()});
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// GET /api/trace/logs
|
|
263
|
-
handlerAdmin.get('/api/trace/logs', async (req, res) => {
|
|
264
|
-
try {
|
|
265
|
-
const limit = Number(req.query.limit) || 100;
|
|
266
|
-
const filter = typeof req.query.filter === 'string' ? req.query.filter : '';
|
|
267
|
-
const logFile = traceManager.getFilePath();
|
|
268
|
-
const lines = await readLastLines(logFile, limit, filter);
|
|
269
|
-
|
|
270
|
-
/** @type {Record<string, unknown>[]} */
|
|
271
|
-
const logs = [];
|
|
272
|
-
for (const line of lines) {
|
|
273
|
-
try {
|
|
274
|
-
const parsed = /** @type {unknown} */ (JSON.parse(line));
|
|
275
|
-
if (parsed && typeof parsed === 'object') {
|
|
276
|
-
logs.push(/** @type {Record<string, unknown>} */ (parsed));
|
|
277
|
-
}
|
|
278
|
-
} catch (e) {
|
|
279
|
-
// ignore
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
res.json(logs.reverse());
|
|
284
|
-
} catch (err) {
|
|
285
|
-
res.status(500).json({error: String(err)});
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// POST /api/trace/clear
|
|
290
|
-
handlerAdmin.post('/api/trace/clear', (_req, res) => {
|
|
291
|
-
traceManager.clear();
|
|
292
|
-
res.json({message: 'Traces cleared'});
|
|
293
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import debugModule from 'debug';
|
|
2
|
-
const debug = debugModule('webplsql:handlerLogger');
|
|
3
|
-
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import morgan from 'morgan';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {import('express').RequestHandler} RequestHandler
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create the upload middleware.
|
|
14
|
-
* @param {string} filename - Output filename.
|
|
15
|
-
* @returns {RequestHandler} - Request handler.
|
|
16
|
-
*/
|
|
17
|
-
export const handlerLogger = (filename) => {
|
|
18
|
-
debug('register');
|
|
19
|
-
|
|
20
|
-
return morgan('combined', {stream: fs.createWriteStream(path.join(process.cwd(), filename), {flags: 'a'})});
|
|
21
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import multer from 'multer';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {import('express').RequestHandler} RequestHandler
|
|
5
|
-
* @typedef {import('express').Response} Response
|
|
6
|
-
* @typedef {import('express').NextFunction} NextFunction
|
|
7
|
-
* @typedef {import('oracledb').Pool} Pool
|
|
8
|
-
* @typedef {import('../types.js').environmentType} environmentType
|
|
9
|
-
* @typedef {import('../types.js').configType} configType
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create the upload middleware.
|
|
14
|
-
* @param {number} [uploadFileSizeLimit] - Maximum size of each uploaded file in bytes or no limit if omitted.
|
|
15
|
-
* @returns {RequestHandler} - Request handler.
|
|
16
|
-
*/
|
|
17
|
-
export const handlerUpload = (uploadFileSizeLimit = Infinity) => {
|
|
18
|
-
const upload = multer({
|
|
19
|
-
storage: multer.diskStorage({}),
|
|
20
|
-
limits: {
|
|
21
|
-
fileSize: uploadFileSizeLimit,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return upload.any();
|
|
26
|
-
};
|
package/src/handler/plsql/cgi.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Prepare the CGI information
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import {URL} from 'node:url';
|
|
7
|
-
import debugModule from 'debug';
|
|
8
|
-
const debug = debugModule('webplsql:cgi');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('express').Request} Request
|
|
12
|
-
* @typedef {import('../../types.js').environmentType} environmentType
|
|
13
|
-
* @typedef {import('../../types.js').configPlSqlType} configPlSqlType
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/** @type {environmentType} */
|
|
17
|
-
const DEFAULT_CGI = {
|
|
18
|
-
PLSQL_GATEWAY: 'WebDb',
|
|
19
|
-
GATEWAY_IVERSION: '2',
|
|
20
|
-
SERVER_SOFTWARE: 'web_plsql',
|
|
21
|
-
GATEWAY_INTERFACE: 'CGI/1.1',
|
|
22
|
-
SERVER_PORT: '',
|
|
23
|
-
SERVER_NAME: os.hostname(),
|
|
24
|
-
REQUEST_METHOD: '',
|
|
25
|
-
PATH_INFO: '',
|
|
26
|
-
SCRIPT_NAME: '',
|
|
27
|
-
REMOTE_ADDR: '',
|
|
28
|
-
SERVER_PROTOCOL: '',
|
|
29
|
-
REQUEST_PROTOCOL: '',
|
|
30
|
-
REMOTE_USER: '',
|
|
31
|
-
HTTP_COOKIE: '',
|
|
32
|
-
HTTP_USER_AGENT: '',
|
|
33
|
-
HTTP_HOST: '',
|
|
34
|
-
HTTP_ACCEPT: '',
|
|
35
|
-
HTTP_ACCEPT_ENCODING: '',
|
|
36
|
-
HTTP_ACCEPT_LANGUAGE: '',
|
|
37
|
-
HTTP_REFERER: '',
|
|
38
|
-
HTTP_X_FORWARDED_FOR: '',
|
|
39
|
-
WEB_AUTHENT_PREFIX: '',
|
|
40
|
-
DAD_NAME: '',
|
|
41
|
-
DOC_ACCESS_PATH: 'doc',
|
|
42
|
-
DOCUMENT_TABLE: '',
|
|
43
|
-
PATH_ALIAS: '',
|
|
44
|
-
REQUEST_CHARSET: 'UTF8',
|
|
45
|
-
REQUEST_IANA_CHARSET: 'UTF-8',
|
|
46
|
-
SCRIPT_PREFIX: '',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create the HTTP_COOKIE string
|
|
51
|
-
*
|
|
52
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
53
|
-
* @returns {string} - The string representation of the cookies.
|
|
54
|
-
*/
|
|
55
|
-
const getCookieString = (req) => {
|
|
56
|
-
let cookieString = '';
|
|
57
|
-
|
|
58
|
-
for (const propName in req.cookies) {
|
|
59
|
-
cookieString += `${propName}=${req.cookies[propName]};`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
debug('getCookieString', req.cookies, cookieString);
|
|
63
|
-
|
|
64
|
-
return cookieString;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get the script name and DAD name from a URL
|
|
69
|
-
*
|
|
70
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
71
|
-
* @returns {{script: string; prefix: string; dad: string}} - The DAD structure.
|
|
72
|
-
*/
|
|
73
|
-
const getPath = (req) => {
|
|
74
|
-
// create a valid url
|
|
75
|
-
const validUrl = `${req.protocol}://${os.hostname()}${req.originalUrl}`;
|
|
76
|
-
|
|
77
|
-
// get the pathname from the url (new URL('https://example.org/abc/xyz?123').pathname => /abc/xyz)
|
|
78
|
-
const pathname = new URL(validUrl).pathname;
|
|
79
|
-
|
|
80
|
-
const tmp = trimPath(pathname.substring(0, pathname.lastIndexOf('/') + 1));
|
|
81
|
-
const script = `/${tmp}`;
|
|
82
|
-
const prefix = `/${tmp.substring(0, tmp.lastIndexOf('/'))}`;
|
|
83
|
-
const dad = tmp.substring(tmp.indexOf('/') + 1);
|
|
84
|
-
|
|
85
|
-
return {script, prefix, dad};
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get a path that is not enclodes in slashes
|
|
90
|
-
*
|
|
91
|
-
* @param {string} value - The value to trim.
|
|
92
|
-
* @returns {string} - The trimmed value.
|
|
93
|
-
*/
|
|
94
|
-
const trimPath = (value) => value.replace(/^\/+|\/+$/g, '');
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create a CGI object
|
|
98
|
-
*
|
|
99
|
-
* @param {Request} req - The req object represents the HTTP request.
|
|
100
|
-
* @param {string} doctable - The document table.
|
|
101
|
-
* @param {environmentType} cgi - The additional cgi.
|
|
102
|
-
* @returns {environmentType} CGI object
|
|
103
|
-
*/
|
|
104
|
-
export const getCGI = (req, doctable, cgi) => {
|
|
105
|
-
const PROTOCOL = req.protocol ? req.protocol.toUpperCase() : '';
|
|
106
|
-
const PATH = getPath(req);
|
|
107
|
-
|
|
108
|
-
/** @type {environmentType} */
|
|
109
|
-
const CGI = {
|
|
110
|
-
SERVER_PORT: typeof req.socket.localPort === 'number' ? req.socket.localPort.toString() : '',
|
|
111
|
-
REQUEST_METHOD: req.method,
|
|
112
|
-
PATH_INFO: Array.isArray(req.params.name) ? (req.params.name[0] ?? '') : (req.params.name ?? ''),
|
|
113
|
-
SCRIPT_NAME: PATH.script,
|
|
114
|
-
REMOTE_ADDR: (req.ip ?? '').replace('::ffff:', ''),
|
|
115
|
-
SERVER_PROTOCOL: `${PROTOCOL}/${req.httpVersion}`,
|
|
116
|
-
REQUEST_PROTOCOL: PROTOCOL,
|
|
117
|
-
HTTP_COOKIE: getCookieString(req),
|
|
118
|
-
HTTP_USER_AGENT: req.get('user-agent') ?? '',
|
|
119
|
-
HTTP_HOST: req.get('host') ?? '',
|
|
120
|
-
HTTP_ACCEPT: req.get('accept') ?? '',
|
|
121
|
-
HTTP_ACCEPT_ENCODING: req.get('accept-encoding') ?? '',
|
|
122
|
-
HTTP_ACCEPT_LANGUAGE: req.get('accept-language') ?? '',
|
|
123
|
-
HTTP_REFERER: req.get('referer') ?? '',
|
|
124
|
-
HTTP_X_FORWARDED_FOR: req.get('x-forwarded-for') ?? '',
|
|
125
|
-
DAD_NAME: PATH.dad,
|
|
126
|
-
DOCUMENT_TABLE: doctable,
|
|
127
|
-
SCRIPT_PREFIX: PATH.prefix,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
return Object.assign({}, DEFAULT_CGI, CGI, cgi);
|
|
131
|
-
};
|