web_plsql 1.0.0 → 1.2.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 +5 -5
- package/package.json +5 -5
- package/src/admin/client/charts.ts +303 -4
- package/src/admin/index.html +283 -69
- package/src/admin/js/api.ts +95 -20
- package/src/admin/js/app.ts +460 -152
- package/src/admin/js/schemas.ts +76 -14
- package/src/admin/js/templates/config.ts +10 -9
- package/src/admin/js/templates/errorRow.ts +8 -5
- package/src/admin/js/templates/index.ts +1 -0
- package/src/admin/js/templates/poolCard.ts +6 -6
- package/src/admin/js/templates/traceRow.ts +24 -0
- package/src/admin/js/types.ts +132 -19
- package/src/admin/js/ui/components.ts +44 -0
- package/src/admin/js/ui/table.ts +173 -0
- package/src/admin/js/ui/views.ts +311 -48
- package/src/admin/js/util/format.ts +22 -3
- package/src/admin/js/util/metrics.ts +24 -0
- package/src/admin/lib/chart.bundle.css +1 -0
- package/src/admin/lib/chart.bundle.js +54 -47
- package/src/admin/style.css +143 -27
- package/src/handler/handlerAdmin.js +121 -26
- package/src/handler/plsql/errorPage.js +0 -4
- package/src/handler/plsql/owaPageStream.js +93 -0
- package/src/handler/plsql/procedure.js +191 -121
- package/src/handler/plsql/procedureNamed.js +17 -3
- package/src/handler/plsql/procedureSanitize.js +24 -0
- package/src/handler/plsql/procedureVariable.js +2 -0
- package/src/handler/plsql/sendResponse.js +41 -3
- package/src/index.js +0 -1
- package/src/server/adminContext.js +23 -0
- package/src/server/server.js +83 -68
- package/src/types.js +1 -1
- package/src/util/statsManager.js +368 -0
- package/src/util/trace.js +2 -1
- package/src/util/traceManager.js +68 -0
- package/src/version.js +1 -1
- package/types/admin/js/schemas.d.ts +384 -0
- package/types/admin/js/types.d.ts +312 -0
- package/types/handler/handlerAdmin.d.ts +70 -0
- package/types/handler/plsql/owaPageStream.d.ts +28 -0
- package/types/handler/plsql/procedure.d.ts +1 -0
- package/types/index.d.ts +0 -1
- package/types/server/adminContext.d.ts +17 -0
- package/types/server/server.d.ts +2 -19
- package/types/types.d.ts +1 -1
- package/types/util/statsManager.d.ts +395 -0
- package/types/util/traceManager.d.ts +35 -0
- package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
- package/src/handler/handlerMetrics.js +0 -68
- package/types/handler/handlerMetrics.d.ts +0 -25
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
[![NPM Version][npm-image]][npm-url]
|
|
2
2
|
[![NPM Downloads][downloads-image]][downloads-url]
|
|
3
|
-
[![
|
|
4
|
-
[![Build status][appveyor-image]][appveyor-url]
|
|
3
|
+
[](https://github.com/doberkofler/web_plsql/actions/workflows/node.js.yml)
|
|
5
4
|
[](https://coveralls.io/github/doberkofler/web_plsql?branch=master)
|
|
6
5
|
|
|
7
6
|
# Oracle PL/SQL Gateway Middleware for the Express web framework for Node.js
|
|
@@ -12,6 +11,8 @@ and serve the content using the Express web framework for Node.js.
|
|
|
12
11
|
|
|
13
12
|
Please feel free to try and suggest any improvements. Your thoughts and ideas are most welcome.
|
|
14
13
|
|
|
14
|
+

|
|
15
|
+
|
|
15
16
|
# Release History
|
|
16
17
|
See the [changelog](https://github.com/doberkofler/web_plsql/blob/master/CHANGELOG.md).
|
|
17
18
|
|
|
@@ -200,6 +201,8 @@ The following mod_plsql DAD configuration translates to the configuration option
|
|
|
200
201
|
- PlsqlRequestValidationFunction -> routePlSql[].pathAliasProcedure
|
|
201
202
|
- PlsqlExclusionList
|
|
202
203
|
- Basic and custom authentication methods, based on the OWA_SEC package and custom packages.
|
|
204
|
+
- Caching of procedure metadata and validation results for high performance.
|
|
205
|
+
- Real-time monitoring and management via a built-in Admin Console.
|
|
203
206
|
|
|
204
207
|
## Features that are only available in web_plsql
|
|
205
208
|
- The option `transactionModeType` specifies an optional transaction mode.
|
|
@@ -233,6 +236,3 @@ The following mod_plsql DAD configuration translates to the configuration option
|
|
|
233
236
|
|
|
234
237
|
[downloads-image]: https://img.shields.io/npm/dm/web_plsql.svg
|
|
235
238
|
[downloads-url]: https://npmjs.org/package/web_plsql
|
|
236
|
-
|
|
237
|
-
[appveyor-image]: https://ci.appveyor.com/api/projects/status/github/doberkofler/web_plsql?branch=master&svg=true
|
|
238
|
-
[appveyor-url]: https://ci.appveyor.com/project/doberkofler/web-plsql
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web_plsql",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"author": "Dieter Oberkofler <dieter.oberkofler@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "The Express Middleware for Oracle PL/SQL",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"build:admin": "vite build",
|
|
48
48
|
"lint": "prettier --check --experimental-cli . && eslint . && tsc --noEmit && eslint src/admin --config src/admin/js/eslint.config.js",
|
|
49
49
|
"test": "vitest run",
|
|
50
|
+
"test:perf": "vitest run tests/performance.test.js",
|
|
50
51
|
"test:coverage": "vitest run --coverage",
|
|
51
52
|
"stylelint": "stylelint \"src/admin/**/*.css\"",
|
|
52
53
|
"types": "rm -rf types && tsc --noEmit false --declaration true --declarationDir types --emitDeclarationOnly true --allowJs true --target es2020 --module es2020 --moduleResolution node --allowSyntheticDefaultImports true src/*.js",
|
|
53
54
|
"clean": "shx rm -f *.tgz && shx rm -f *.log",
|
|
54
55
|
"version": "node update-version.js",
|
|
55
|
-
"prepack": "npm run ci",
|
|
56
56
|
"ci": "npm run clean && npm run version && npm run types && npm run build && npm run lint && npm run stylelint && npm run test:coverage",
|
|
57
|
-
"create-package": "shx rm -f *.tgz && npm pack",
|
|
57
|
+
"create-package": "npm run ci && shx rm -f *.tgz && npm pack",
|
|
58
58
|
"image-build": "docker build --no-cache --progress=plain --tag=web_plsql .",
|
|
59
59
|
"image-save": "docker save web_plsql | gzip > web_plsql.tar.gz"
|
|
60
60
|
},
|
|
@@ -80,12 +80,12 @@
|
|
|
80
80
|
"@types/debug": "4.1.12",
|
|
81
81
|
"@types/morgan": "1.9.10",
|
|
82
82
|
"@types/multer": "2.0.0",
|
|
83
|
-
"@types/node": "25.2.
|
|
83
|
+
"@types/node": "25.2.2",
|
|
84
84
|
"@types/oracledb": "6.10.1",
|
|
85
85
|
"@types/supertest": "6.0.3",
|
|
86
86
|
"@vitest/coverage-v8": "^4.0.18",
|
|
87
87
|
"eslint": "9.39.2",
|
|
88
|
-
"eslint-plugin-jsdoc": "62.5.
|
|
88
|
+
"eslint-plugin-jsdoc": "62.5.4",
|
|
89
89
|
"oracledb": "6.10.0",
|
|
90
90
|
"prettier": "3.8.1",
|
|
91
91
|
"shx": "0.4.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Chart, registerables} from 'chart.js';
|
|
2
|
-
import type {State, ChartInstance} from '../js/types.js';
|
|
2
|
+
import type {State, ChartInstance, HistoryBucket} from '../js/types.js';
|
|
3
3
|
import type {StatusResponse} from '../js/schemas.js';
|
|
4
4
|
|
|
5
5
|
// Register Chart.js components
|
|
@@ -40,6 +40,182 @@ export function initCharts(state: State): void {
|
|
|
40
40
|
const isDark = document.body.classList.contains('dark');
|
|
41
41
|
const colors = getChartColors(isDark);
|
|
42
42
|
|
|
43
|
+
const memoryEl = document.getElementById('memory-chart') as HTMLCanvasElement | null;
|
|
44
|
+
if (memoryEl) {
|
|
45
|
+
const chartData = {
|
|
46
|
+
labels: [],
|
|
47
|
+
datasets: [
|
|
48
|
+
{
|
|
49
|
+
label: 'Memory Usage (%)',
|
|
50
|
+
data: [],
|
|
51
|
+
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
52
|
+
borderColor: '#10b981',
|
|
53
|
+
borderWidth: 2,
|
|
54
|
+
fill: true,
|
|
55
|
+
tension: 0.4,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const chartOptions = {
|
|
61
|
+
responsive: true,
|
|
62
|
+
maintainAspectRatio: false,
|
|
63
|
+
scales: {
|
|
64
|
+
x: {
|
|
65
|
+
grid: {color: colors.gridColor},
|
|
66
|
+
ticks: {
|
|
67
|
+
display: false,
|
|
68
|
+
color: colors.textColor,
|
|
69
|
+
maxRotation: 0,
|
|
70
|
+
autoSkip: true,
|
|
71
|
+
maxTicksLimit: 10,
|
|
72
|
+
},
|
|
73
|
+
title: {
|
|
74
|
+
display: false,
|
|
75
|
+
text: 'Time',
|
|
76
|
+
color: colors.textColor,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
y: {
|
|
80
|
+
display: true,
|
|
81
|
+
beginAtZero: true,
|
|
82
|
+
max: 100,
|
|
83
|
+
grid: {
|
|
84
|
+
color: colors.gridColor,
|
|
85
|
+
},
|
|
86
|
+
border: {
|
|
87
|
+
display: true,
|
|
88
|
+
color: colors.gridColor,
|
|
89
|
+
},
|
|
90
|
+
ticks: {
|
|
91
|
+
display: true,
|
|
92
|
+
color: colors.textColor,
|
|
93
|
+
stepSize: 25,
|
|
94
|
+
callback: function (value: number | string) {
|
|
95
|
+
return String(value) + '%';
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
title: {
|
|
99
|
+
display: false,
|
|
100
|
+
text: 'Memory %',
|
|
101
|
+
color: colors.textColor,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
layout: {
|
|
106
|
+
padding: 0,
|
|
107
|
+
},
|
|
108
|
+
plugins: {
|
|
109
|
+
legend: {display: false},
|
|
110
|
+
tooltip: {
|
|
111
|
+
enabled: true,
|
|
112
|
+
position: 'nearest' as const,
|
|
113
|
+
intersect: false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const chart = new Chart(memoryEl, {
|
|
119
|
+
type: 'line',
|
|
120
|
+
data: chartData,
|
|
121
|
+
options: chartOptions,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
state.charts.memory = {
|
|
125
|
+
data: chartData,
|
|
126
|
+
options: chartOptions,
|
|
127
|
+
update: () => chart.update(),
|
|
128
|
+
} as ChartInstance;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cpuEl = document.getElementById('cpu-chart') as HTMLCanvasElement | null;
|
|
132
|
+
if (cpuEl) {
|
|
133
|
+
const chartData = {
|
|
134
|
+
labels: [],
|
|
135
|
+
datasets: [
|
|
136
|
+
{
|
|
137
|
+
label: 'CPU Usage (%)',
|
|
138
|
+
data: [],
|
|
139
|
+
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
140
|
+
borderColor: '#10b981',
|
|
141
|
+
borderWidth: 2,
|
|
142
|
+
fill: true,
|
|
143
|
+
tension: 0.4,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const chartOptions = {
|
|
149
|
+
responsive: true,
|
|
150
|
+
maintainAspectRatio: false,
|
|
151
|
+
scales: {
|
|
152
|
+
x: {
|
|
153
|
+
grid: {color: colors.gridColor},
|
|
154
|
+
ticks: {
|
|
155
|
+
display: false,
|
|
156
|
+
color: colors.textColor,
|
|
157
|
+
maxRotation: 0,
|
|
158
|
+
autoSkip: true,
|
|
159
|
+
maxTicksLimit: 10,
|
|
160
|
+
},
|
|
161
|
+
title: {
|
|
162
|
+
display: false,
|
|
163
|
+
text: 'Time',
|
|
164
|
+
color: colors.textColor,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
y: {
|
|
168
|
+
display: true,
|
|
169
|
+
beginAtZero: true,
|
|
170
|
+
max: 100,
|
|
171
|
+
grid: {
|
|
172
|
+
color: colors.gridColor,
|
|
173
|
+
},
|
|
174
|
+
border: {
|
|
175
|
+
display: true,
|
|
176
|
+
color: colors.gridColor,
|
|
177
|
+
},
|
|
178
|
+
ticks: {
|
|
179
|
+
display: true,
|
|
180
|
+
color: colors.textColor,
|
|
181
|
+
stepSize: 25,
|
|
182
|
+
callback: function (value: number | string) {
|
|
183
|
+
return String(value) + '%';
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
title: {
|
|
187
|
+
display: false,
|
|
188
|
+
text: 'Cpu %',
|
|
189
|
+
color: colors.textColor,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
layout: {
|
|
194
|
+
padding: 0,
|
|
195
|
+
},
|
|
196
|
+
plugins: {
|
|
197
|
+
legend: {display: false},
|
|
198
|
+
tooltip: {
|
|
199
|
+
enabled: true,
|
|
200
|
+
position: 'nearest' as const,
|
|
201
|
+
intersect: false,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const chart = new Chart(cpuEl, {
|
|
207
|
+
type: 'line',
|
|
208
|
+
data: chartData,
|
|
209
|
+
options: chartOptions,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
state.charts.cpu = {
|
|
213
|
+
data: chartData,
|
|
214
|
+
options: chartOptions,
|
|
215
|
+
update: () => chart.update(),
|
|
216
|
+
} as ChartInstance;
|
|
217
|
+
}
|
|
218
|
+
|
|
43
219
|
const trafficEl = document.getElementById('traffic-chart') as HTMLCanvasElement | null;
|
|
44
220
|
if (trafficEl) {
|
|
45
221
|
const chartData = {
|
|
@@ -73,6 +249,7 @@ export function initCharts(state: State): void {
|
|
|
73
249
|
x: {
|
|
74
250
|
grid: {color: colors.gridColor},
|
|
75
251
|
ticks: {
|
|
252
|
+
display: false,
|
|
76
253
|
color: colors.textColor,
|
|
77
254
|
maxRotation: 0,
|
|
78
255
|
autoSkip: true,
|
|
@@ -145,6 +322,7 @@ export function initCharts(state: State): void {
|
|
|
145
322
|
x: {
|
|
146
323
|
grid: {color: colors.gridColor},
|
|
147
324
|
ticks: {
|
|
325
|
+
display: false,
|
|
148
326
|
color: colors.textColor,
|
|
149
327
|
maxRotation: 0,
|
|
150
328
|
autoSkip: true,
|
|
@@ -188,6 +366,99 @@ export function initCharts(state: State): void {
|
|
|
188
366
|
}
|
|
189
367
|
}
|
|
190
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Hydrate charts and state from server history.
|
|
371
|
+
* @param state - Application state.
|
|
372
|
+
* @param history - Server history buffer.
|
|
373
|
+
*/
|
|
374
|
+
export function hydrateHistory(state: State, history: HistoryBucket[]): void {
|
|
375
|
+
state.history.labels = [];
|
|
376
|
+
state.history.requests = [];
|
|
377
|
+
state.history.avgResponseTimes = [];
|
|
378
|
+
state.history.p95ResponseTimes = [];
|
|
379
|
+
state.history.p99ResponseTimes = [];
|
|
380
|
+
state.history.cpuUsage = [];
|
|
381
|
+
state.history.memoryUsage = [];
|
|
382
|
+
state.history.poolUsage = {};
|
|
383
|
+
|
|
384
|
+
const maxPoints = state.maxHistoryPoints;
|
|
385
|
+
const recentHistory = history.slice(-maxPoints);
|
|
386
|
+
const intervalSec = (state.status.intervalMs ?? 5000) / 1000;
|
|
387
|
+
|
|
388
|
+
recentHistory.forEach((b) => {
|
|
389
|
+
const timeLabel = new Date(b.timestamp).toLocaleTimeString();
|
|
390
|
+
state.history.labels.push(timeLabel);
|
|
391
|
+
state.history.requests.push(b.requests / intervalSec); // Requests per second
|
|
392
|
+
state.history.avgResponseTimes.push(b.durationAvg);
|
|
393
|
+
state.history.p95ResponseTimes ??= [];
|
|
394
|
+
state.history.p95ResponseTimes.push(b.durationP95);
|
|
395
|
+
state.history.p99ResponseTimes ??= [];
|
|
396
|
+
state.history.p99ResponseTimes.push(b.durationP99);
|
|
397
|
+
|
|
398
|
+
// Resource usage percentage
|
|
399
|
+
const totalMem = state.status.system?.memory.totalMemory ?? 0;
|
|
400
|
+
state.history.cpuUsage.push(b.system.cpu);
|
|
401
|
+
state.history.memoryUsage.push(totalMem > 0 ? (b.system.rss / totalMem) * 100 : 0);
|
|
402
|
+
|
|
403
|
+
b.pools.forEach((p) => {
|
|
404
|
+
state.history.poolUsage[p.name] ??= [];
|
|
405
|
+
state.history.poolUsage[p.name]?.push(p.connectionsInUse);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const trafficChart = state.charts.traffic;
|
|
410
|
+
if (trafficChart) {
|
|
411
|
+
trafficChart.data.labels = state.history.labels;
|
|
412
|
+
const ds0 = trafficChart.data.datasets[0];
|
|
413
|
+
const ds1 = trafficChart.data.datasets[1];
|
|
414
|
+
if (ds0) ds0.data = state.history.requests;
|
|
415
|
+
if (ds1) ds1.data = state.history.avgResponseTimes;
|
|
416
|
+
trafficChart.update();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const poolChart = state.charts.pool;
|
|
420
|
+
if (poolChart) {
|
|
421
|
+
poolChart.data.labels = state.history.labels;
|
|
422
|
+
Object.entries(state.history.poolUsage).forEach(([name, usage], i) => {
|
|
423
|
+
let dataset = poolChart.data.datasets.find((ds) => ds.label === name);
|
|
424
|
+
if (!dataset) {
|
|
425
|
+
const colors = ['#10b981', '#f59e0b', '#3b82f6', '#8b5cf6', '#ec4899'];
|
|
426
|
+
const color = colors[i % colors.length] ?? '#3b82f6';
|
|
427
|
+
dataset = {
|
|
428
|
+
label: name,
|
|
429
|
+
data: usage,
|
|
430
|
+
borderColor: color,
|
|
431
|
+
backgroundColor: undefined,
|
|
432
|
+
fill: undefined,
|
|
433
|
+
tension: 0.4,
|
|
434
|
+
};
|
|
435
|
+
poolChart.data.datasets.push(dataset);
|
|
436
|
+
} else {
|
|
437
|
+
dataset.data = usage;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
poolChart.update();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const memoryChart = state.charts.memory;
|
|
444
|
+
if (memoryChart) {
|
|
445
|
+
memoryChart.data.labels = state.history.labels;
|
|
446
|
+
if (memoryChart.data.datasets[0]) {
|
|
447
|
+
memoryChart.data.datasets[0].data = state.history.memoryUsage;
|
|
448
|
+
}
|
|
449
|
+
memoryChart.update();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const cpuChart = state.charts.cpu;
|
|
453
|
+
if (cpuChart) {
|
|
454
|
+
cpuChart.data.labels = state.history.labels;
|
|
455
|
+
if (cpuChart.data.datasets[0]) {
|
|
456
|
+
cpuChart.data.datasets[0].data = state.history.cpuUsage;
|
|
457
|
+
}
|
|
458
|
+
cpuChart.update();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
191
462
|
/**
|
|
192
463
|
* Update charts with new data.
|
|
193
464
|
* @param state - Application state.
|
|
@@ -203,10 +474,23 @@ export function updateCharts(state: State, timeLabel: string, reqPerSec: number,
|
|
|
203
474
|
state.history.requests.push(reqPerSec);
|
|
204
475
|
state.history.avgResponseTimes.push(avgResponseTime);
|
|
205
476
|
|
|
477
|
+
// Resource usage percentage
|
|
478
|
+
const totalMem = state.status.system?.memory.totalMemory ?? 0;
|
|
479
|
+
const latestBucket = state.status.history?.[state.status.history.length - 1];
|
|
480
|
+
if (latestBucket) {
|
|
481
|
+
state.history.cpuUsage.push(latestBucket.system.cpu);
|
|
482
|
+
state.history.memoryUsage.push(totalMem > 0 ? (latestBucket.system.rss / totalMem) * 100 : 0);
|
|
483
|
+
} else {
|
|
484
|
+
state.history.cpuUsage.push(0);
|
|
485
|
+
state.history.memoryUsage.push(0);
|
|
486
|
+
}
|
|
487
|
+
|
|
206
488
|
if (state.history.labels.length > maxPoints) {
|
|
207
489
|
state.history.labels.shift();
|
|
208
490
|
state.history.requests.shift();
|
|
209
491
|
state.history.avgResponseTimes.shift();
|
|
492
|
+
state.history.cpuUsage.shift();
|
|
493
|
+
state.history.memoryUsage.shift();
|
|
210
494
|
}
|
|
211
495
|
|
|
212
496
|
const trafficChart = state.charts.traffic;
|
|
@@ -217,6 +501,20 @@ export function updateCharts(state: State, timeLabel: string, reqPerSec: number,
|
|
|
217
501
|
trafficChart.update();
|
|
218
502
|
}
|
|
219
503
|
|
|
504
|
+
const memoryChart = state.charts.memory;
|
|
505
|
+
if (memoryChart?.data.datasets[0]) {
|
|
506
|
+
memoryChart.data.labels = state.history.labels;
|
|
507
|
+
memoryChart.data.datasets[0].data = state.history.memoryUsage;
|
|
508
|
+
memoryChart.update();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const cpuChart = state.charts.cpu;
|
|
512
|
+
if (cpuChart?.data.datasets[0]) {
|
|
513
|
+
cpuChart.data.labels = state.history.labels;
|
|
514
|
+
cpuChart.data.datasets[0].data = state.history.cpuUsage;
|
|
515
|
+
cpuChart.update();
|
|
516
|
+
}
|
|
517
|
+
|
|
220
518
|
const poolChart = state.charts.pool;
|
|
221
519
|
if (poolChart) {
|
|
222
520
|
poolChart.data.labels = state.history.labels;
|
|
@@ -228,7 +526,7 @@ export function updateCharts(state: State, timeLabel: string, reqPerSec: number,
|
|
|
228
526
|
usage = [];
|
|
229
527
|
history.poolUsage[poolName] = usage;
|
|
230
528
|
const colors = ['#10b981', '#f59e0b', '#3b82f6', '#8b5cf6', '#ec4899'];
|
|
231
|
-
const color = colors[i % colors.length];
|
|
529
|
+
const color = colors[i % colors.length] ?? '#3b82f6';
|
|
232
530
|
if (color) {
|
|
233
531
|
poolChart.data.datasets.push({
|
|
234
532
|
label: poolName,
|
|
@@ -250,7 +548,8 @@ export function updateCharts(state: State, timeLabel: string, reqPerSec: number,
|
|
|
250
548
|
}
|
|
251
549
|
|
|
252
550
|
/**
|
|
253
|
-
*
|
|
551
|
+
* Render a cache statistics pie chart.
|
|
552
|
+
*
|
|
254
553
|
* @param canvas - The canvas element.
|
|
255
554
|
*/
|
|
256
555
|
export function renderCachePie(canvas: HTMLCanvasElement): void {
|
|
@@ -287,7 +586,7 @@ export function renderCachePie(canvas: HTMLCanvasElement): void {
|
|
|
287
586
|
const value = context.raw as number;
|
|
288
587
|
const total = hits + misses;
|
|
289
588
|
const pct = total > 0 ? ((value / total) * 100).toFixed(1) : '0';
|
|
290
|
-
return `${context.label}: ${value} (${pct}%)`;
|
|
589
|
+
return `${context.label ?? ''}: ${value} (${pct}%)`;
|
|
291
590
|
},
|
|
292
591
|
},
|
|
293
592
|
},
|