web_plsql 0.18.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 +29 -9
- package/package.json +17 -7
- package/src/admin/client/charts.ts +598 -0
- package/src/admin/client/main.js +3 -0
- package/src/admin/client/tailwind.css +41 -0
- package/src/admin/favicon.svg +3 -0
- package/src/admin/index.html +529 -0
- package/src/admin/js/api.ts +170 -0
- package/src/admin/js/app.ts +614 -0
- package/src/admin/js/eslint.config.js +74 -0
- package/src/admin/js/schemas.ts +215 -0
- package/src/admin/js/templates/config.ts +147 -0
- package/src/admin/js/templates/errorRow.ts +21 -0
- package/src/admin/js/templates/index.ts +4 -0
- package/src/admin/js/templates/poolCard.ts +61 -0
- package/src/admin/js/templates/traceRow.ts +24 -0
- package/src/admin/js/tsconfig.json +24 -0
- package/src/admin/js/types.ts +336 -0
- package/src/admin/js/ui/components.ts +44 -0
- package/src/admin/js/ui/table.ts +173 -0
- package/src/admin/js/ui/theme.ts +93 -0
- package/src/admin/js/ui/views.ts +427 -0
- package/src/admin/js/util/format.ts +46 -0
- 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 +146 -0
- package/src/admin/style.css +1437 -0
- package/src/bin/load-test.js +202 -0
- package/src/handler/handlerAdmin.js +293 -0
- package/src/handler/plsql/cgi.js +1 -1
- package/src/handler/plsql/errorPage.js +31 -6
- package/src/handler/plsql/handlerPlSql.js +32 -6
- package/src/handler/plsql/owaPageStream.js +93 -0
- package/src/handler/plsql/parsePage.js +7 -3
- package/src/handler/plsql/procedure.js +221 -105
- package/src/handler/plsql/procedureNamed.js +34 -49
- package/src/handler/plsql/procedureSanitize.js +120 -45
- package/src/handler/plsql/procedureVariable.js +4 -2
- package/src/handler/plsql/request.js +8 -3
- package/src/handler/plsql/sendResponse.js +43 -5
- package/src/index.js +0 -1
- package/src/server/adminContext.js +23 -0
- package/src/server/config.js +1 -0
- package/src/server/server.js +130 -9
- package/src/types.js +7 -1
- package/src/util/cache.js +123 -0
- package/src/util/jsonLogger.js +47 -0
- package/src/util/shutdown.js +6 -2
- package/src/util/statsManager.js +368 -0
- package/src/util/trace.js +7 -6
- 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 +79 -0
- package/types/handler/plsql/errorPage.d.ts +1 -0
- package/types/handler/plsql/handlerPlSql.d.ts +6 -1
- package/types/handler/plsql/owaPageStream.d.ts +28 -0
- package/types/handler/plsql/procedure.d.ts +4 -1
- package/types/handler/plsql/procedureNamed.d.ts +2 -5
- package/types/handler/plsql/procedureSanitize.d.ts +2 -5
- package/types/handler/plsql/procedureVariable.d.ts +1 -1
- package/types/handler/plsql/request.d.ts +3 -1
- package/types/handler/plsql/sendResponse.d.ts +1 -1
- package/types/index.d.ts +0 -1
- package/types/server/adminContext.d.ts +17 -0
- package/types/server/server.d.ts +5 -0
- package/types/types.d.ts +19 -1
- package/types/util/cache.d.ts +69 -0
- package/types/util/jsonLogger.d.ts +45 -0
- package/types/util/statsManager.d.ts +395 -0
- package/types/util/traceManager.d.ts +35 -0
- package/src/handler/handlerMetrics.js +0 -68
- package/types/handler/handlerMetrics.d.ts +0 -25
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {z} from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cache statistics schema.
|
|
5
|
+
*/
|
|
6
|
+
export const cacheStatsSchema = z.object({
|
|
7
|
+
size: z.number(),
|
|
8
|
+
hits: z.number(),
|
|
9
|
+
misses: z.number(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pool cache snapshot schema.
|
|
14
|
+
*/
|
|
15
|
+
export const poolCacheSnapshotSchema = z.object({
|
|
16
|
+
procedureName: cacheStatsSchema,
|
|
17
|
+
argument: cacheStatsSchema,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pool statistics schema.
|
|
22
|
+
*/
|
|
23
|
+
export const poolStatsSchema = z.object({
|
|
24
|
+
totalRequests: z.number(),
|
|
25
|
+
totalTimeouts: z.number(),
|
|
26
|
+
totalRequestsEnqueued: z.number().optional(),
|
|
27
|
+
totalRequestsDequeued: z.number().optional(),
|
|
28
|
+
totalRequestsFailed: z.number().optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pool information schema.
|
|
33
|
+
*/
|
|
34
|
+
export const poolInfoSchema = z.object({
|
|
35
|
+
name: z.string(),
|
|
36
|
+
connectionsInUse: z.number(),
|
|
37
|
+
connectionsOpen: z.number(),
|
|
38
|
+
stats: poolStatsSchema.nullable(),
|
|
39
|
+
cache: poolCacheSnapshotSchema.optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Server metrics schema.
|
|
44
|
+
*/
|
|
45
|
+
export const metricsSchema = z.object({
|
|
46
|
+
requestCount: z.number(),
|
|
47
|
+
errorCount: z.number(),
|
|
48
|
+
avgResponseTime: z.number(),
|
|
49
|
+
minResponseTime: z.number(),
|
|
50
|
+
maxResponseTime: z.number(),
|
|
51
|
+
maxRequestsPerSecond: z.number(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Historical bucket schema.
|
|
56
|
+
*/
|
|
57
|
+
export const bucketSchema = z.object({
|
|
58
|
+
timestamp: z.number(),
|
|
59
|
+
requests: z.number(),
|
|
60
|
+
errors: z.number(),
|
|
61
|
+
durationMin: z.number(),
|
|
62
|
+
durationMax: z.number(),
|
|
63
|
+
durationAvg: z.number(),
|
|
64
|
+
durationP95: z.number(),
|
|
65
|
+
durationP99: z.number(),
|
|
66
|
+
system: z.object({
|
|
67
|
+
cpu: z.number(),
|
|
68
|
+
heapUsed: z.number(),
|
|
69
|
+
heapTotal: z.number(),
|
|
70
|
+
rss: z.number(),
|
|
71
|
+
external: z.number(),
|
|
72
|
+
}),
|
|
73
|
+
pools: z.array(
|
|
74
|
+
z.object({
|
|
75
|
+
name: z.string(),
|
|
76
|
+
connectionsInUse: z.number(),
|
|
77
|
+
connectionsOpen: z.number(),
|
|
78
|
+
cache: poolCacheSnapshotSchema.optional(),
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Route configuration schema.
|
|
85
|
+
*/
|
|
86
|
+
export const routeConfigSchema = z.object({
|
|
87
|
+
route: z.string(),
|
|
88
|
+
// PL/SQL specific
|
|
89
|
+
user: z.string().optional(),
|
|
90
|
+
password: z.string().optional(),
|
|
91
|
+
connectString: z.string().optional(),
|
|
92
|
+
defaultPage: z.string().optional(),
|
|
93
|
+
pathAlias: z.string().optional(),
|
|
94
|
+
pathAliasProcedure: z.string().optional(),
|
|
95
|
+
documentTable: z.string().optional(),
|
|
96
|
+
exclusionList: z.array(z.string()).optional(),
|
|
97
|
+
requestValidationFunction: z.string().optional(),
|
|
98
|
+
transactionMode: z.unknown().optional(),
|
|
99
|
+
errorStyle: z.string().optional(),
|
|
100
|
+
// Static specific
|
|
101
|
+
directoryPath: z.string().optional(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Server configuration schema.
|
|
106
|
+
*/
|
|
107
|
+
export const serverConfigSchema = z.object({
|
|
108
|
+
port: z.number(),
|
|
109
|
+
adminRoute: z.string().optional(),
|
|
110
|
+
adminUser: z.string().optional(),
|
|
111
|
+
adminPassword: z.string().optional(),
|
|
112
|
+
loggerFilename: z.string(),
|
|
113
|
+
uploadFileSizeLimit: z.number().optional(),
|
|
114
|
+
routePlSql: z.array(routeConfigSchema),
|
|
115
|
+
routeStatic: z.array(routeConfigSchema),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* System information schema.
|
|
120
|
+
*/
|
|
121
|
+
export const systemInfoSchema = z.object({
|
|
122
|
+
nodeVersion: z.string(),
|
|
123
|
+
platform: z.string(),
|
|
124
|
+
arch: z.string(),
|
|
125
|
+
cpuCores: z.number().optional(),
|
|
126
|
+
memory: z.object({
|
|
127
|
+
rss: z.number(),
|
|
128
|
+
heapTotal: z.number(),
|
|
129
|
+
heapUsed: z.number(),
|
|
130
|
+
external: z.number(),
|
|
131
|
+
totalMemory: z.number().optional(),
|
|
132
|
+
rssMax: z.number().optional(),
|
|
133
|
+
heapTotalMax: z.number().optional(),
|
|
134
|
+
heapUsedMax: z.number().optional(),
|
|
135
|
+
externalMax: z.number().optional(),
|
|
136
|
+
}),
|
|
137
|
+
cpu: z.object({
|
|
138
|
+
user: z.number(),
|
|
139
|
+
system: z.number(),
|
|
140
|
+
max: z.number().optional(),
|
|
141
|
+
userMax: z.number().optional(),
|
|
142
|
+
systemMax: z.number().optional(),
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Server status response schema.
|
|
148
|
+
*/
|
|
149
|
+
export const statusSchema = z.object({
|
|
150
|
+
version: z.string(),
|
|
151
|
+
status: z.enum(['running', 'paused', 'stopped']),
|
|
152
|
+
uptime: z.number(),
|
|
153
|
+
startTime: z.string(),
|
|
154
|
+
intervalMs: z.number().optional(),
|
|
155
|
+
metrics: metricsSchema,
|
|
156
|
+
history: z.array(bucketSchema).optional(),
|
|
157
|
+
pools: z.array(poolInfoSchema),
|
|
158
|
+
system: systemInfoSchema,
|
|
159
|
+
config: serverConfigSchema.partial(),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Error log entry schema.
|
|
164
|
+
*/
|
|
165
|
+
export const errorLogSchema = z.object({
|
|
166
|
+
timestamp: z.string(),
|
|
167
|
+
req: z
|
|
168
|
+
.object({
|
|
169
|
+
method: z.string().optional(),
|
|
170
|
+
url: z.string().optional(),
|
|
171
|
+
})
|
|
172
|
+
.optional(),
|
|
173
|
+
message: z.string(),
|
|
174
|
+
details: z
|
|
175
|
+
.object({
|
|
176
|
+
fullMessage: z.string().optional(),
|
|
177
|
+
})
|
|
178
|
+
.optional(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export const accessLogResponseSchema = z.union([z.array(z.string()), z.object({message: z.string()})]);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Trace entry schema.
|
|
185
|
+
*/
|
|
186
|
+
export const traceEntrySchema = z.object({
|
|
187
|
+
id: z.string(),
|
|
188
|
+
timestamp: z.string(),
|
|
189
|
+
source: z.string(),
|
|
190
|
+
url: z.string(),
|
|
191
|
+
method: z.string(),
|
|
192
|
+
status: z.string(),
|
|
193
|
+
duration: z.number(),
|
|
194
|
+
procedure: z.string().optional(),
|
|
195
|
+
parameters: z.record(z.string(), z.any()).optional(),
|
|
196
|
+
uploads: z.array(z.any()).optional(),
|
|
197
|
+
downloads: z
|
|
198
|
+
.object({
|
|
199
|
+
fileType: z.string(),
|
|
200
|
+
fileSize: z.number(),
|
|
201
|
+
})
|
|
202
|
+
.optional(),
|
|
203
|
+
html: z.string().optional(),
|
|
204
|
+
cookies: z.record(z.string(), z.string()).optional(),
|
|
205
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
206
|
+
cgi: z.record(z.string(), z.string()).optional(),
|
|
207
|
+
error: z.string().optional(),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* API response type helpers.
|
|
212
|
+
*/
|
|
213
|
+
export type StatusResponse = z.infer<typeof statusSchema>;
|
|
214
|
+
export type ErrorLogResponse = z.infer<typeof errorLogSchema>;
|
|
215
|
+
export type AccessLogResponse = z.infer<typeof accessLogResponseSchema>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type {ServerConfig, RouteConfig} from '../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Render a stat row using the standardized format.
|
|
5
|
+
* @param label - The field label.
|
|
6
|
+
* @param value - The value to display.
|
|
7
|
+
* @param isMono - Whether to use monospaced font.
|
|
8
|
+
* @param colorClass - The CSS class for coloring the value.
|
|
9
|
+
* @param title - Optional tooltip explanation.
|
|
10
|
+
* @returns HTML string.
|
|
11
|
+
*/
|
|
12
|
+
function renderStatRow(label: string, value: string | number, isMono = true, colorClass = 'text-accent', title = ''): string {
|
|
13
|
+
const valClass = `${isMono ? 'font-mono' : ''} ${colorClass} font-bold break-all`.trim();
|
|
14
|
+
return `
|
|
15
|
+
<div class="stat-row text-sm" ${title ? `title="${title}"` : ''}>
|
|
16
|
+
<span>${label}</span>
|
|
17
|
+
<span class="${valClass}">${value}</span>
|
|
18
|
+
</div>
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Render a configuration section header.
|
|
24
|
+
* @param title - The section title.
|
|
25
|
+
* @param icon - The Material icon name.
|
|
26
|
+
* @returns HTML string.
|
|
27
|
+
*/
|
|
28
|
+
function renderSectionTitle(title: string, icon: string): string {
|
|
29
|
+
return `
|
|
30
|
+
<div class="card-header mt-6 mb-2">
|
|
31
|
+
<span class="material-symbols-rounded icon-sm">${icon}</span>
|
|
32
|
+
<h3 class="text-xs uppercase tracking-wider font-bold text-accent">${title}</h3>
|
|
33
|
+
</div>
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Render a PL/SQL route card.
|
|
39
|
+
* @param r - The route configuration.
|
|
40
|
+
* @param index - The index of the route.
|
|
41
|
+
* @returns HTML string.
|
|
42
|
+
*/
|
|
43
|
+
function renderPlSqlRoute(r: RouteConfig, index: number): string {
|
|
44
|
+
const exclusionList = r.exclusionList?.length ? r.exclusionList.join(', ') : '(None)';
|
|
45
|
+
const transactionMode = typeof r.transactionMode === 'string' ? r.transactionMode : r.transactionMode ? 'Custom' : 'commit';
|
|
46
|
+
|
|
47
|
+
return `
|
|
48
|
+
<div class="card mb-6">
|
|
49
|
+
<div class="flex justify-between items-center mb-4">
|
|
50
|
+
<div class="flex items-center gap-3">
|
|
51
|
+
<span class="badge badge-info">PL/SQL Route #${index + 1}</span>
|
|
52
|
+
<code class="text-lg font-bold text-bright">${r.route}</code>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
57
|
+
<div>
|
|
58
|
+
<h4 class="text-xs font-bold text-main uppercase mb-3 opacity-75">Database Connection</h4>
|
|
59
|
+
${renderStatRow('Oracle User', r.user ?? '(Not set)')}
|
|
60
|
+
${renderStatRow('Connect String', r.connectString ?? '(Not set)')}
|
|
61
|
+
${renderStatRow('Password', r.password ?? '(None)')}
|
|
62
|
+
</div>
|
|
63
|
+
<div>
|
|
64
|
+
<h4 class="text-xs font-bold text-main uppercase mb-3 opacity-75">Request Handler</h4>
|
|
65
|
+
${renderStatRow('Default Page', r.defaultPage ?? 'index')}
|
|
66
|
+
${renderStatRow('Document Table', r.documentTable ?? '(None)')}
|
|
67
|
+
${renderStatRow('Error Style', r.errorStyle ?? 'table')}
|
|
68
|
+
${renderStatRow('Transaction Mode', transactionMode)}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="mt-4 pt-4 border-t">
|
|
73
|
+
<h4 class="text-xs font-bold text-main uppercase mb-3 opacity-75">Path & Validation</h4>
|
|
74
|
+
${renderStatRow('Path Alias', r.pathAlias ?? '(None)')}
|
|
75
|
+
${renderStatRow('Path Alias Proc', r.pathAliasProcedure ?? '(None)')}
|
|
76
|
+
${renderStatRow('Exclusion List', exclusionList)}
|
|
77
|
+
${renderStatRow('Validation Func', r.requestValidationFunction ?? '(None)')}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Render a Static route card.
|
|
85
|
+
* @param r - The route configuration.
|
|
86
|
+
* @param index - The index of the route.
|
|
87
|
+
* @returns HTML string.
|
|
88
|
+
*/
|
|
89
|
+
function renderStaticRoute(r: RouteConfig, index: number): string {
|
|
90
|
+
return `
|
|
91
|
+
<div class="card mb-4">
|
|
92
|
+
<div class="flex items-center gap-3 mb-4">
|
|
93
|
+
<span class="badge badge-success">Static Route #${index + 1}</span>
|
|
94
|
+
<code class="text-lg font-bold text-bright">${r.route}</code>
|
|
95
|
+
</div>
|
|
96
|
+
${renderStatRow('Directory Path', r.directoryPath ?? '(Not set)')}
|
|
97
|
+
</div>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Render the full server configuration.
|
|
103
|
+
* @param config - The server configuration.
|
|
104
|
+
* @returns HTML string.
|
|
105
|
+
*/
|
|
106
|
+
export function renderConfig(config: Partial<ServerConfig>): string {
|
|
107
|
+
let html = '<div class="config-view-content">';
|
|
108
|
+
|
|
109
|
+
// Global Server Section
|
|
110
|
+
html += '<div class="card mb-8">';
|
|
111
|
+
html += '<div class="card-header"><span class="material-symbols-rounded">dns</span><h3>Server Settings</h3></div>';
|
|
112
|
+
if (typeof config.port === 'number') {
|
|
113
|
+
html += renderStatRow('Port', config.port, true, 'text-accent', 'The TCP port the server is listening on');
|
|
114
|
+
}
|
|
115
|
+
html += renderStatRow('Admin Route', config.adminRoute ?? '/admin', true, 'text-accent', 'The URL path for the administration console');
|
|
116
|
+
html += renderStatRow('Admin User', config.adminUser ?? '(Not authenticated)', true, 'text-accent', 'The username required for admin access');
|
|
117
|
+
html += renderStatRow('Admin Password', config.adminPassword ?? '(None)', true, 'text-accent', 'The password required for admin access');
|
|
118
|
+
html += renderStatRow('Logger Filename', config.loggerFilename ?? '(Logging disabled)', true, 'text-accent', 'The path to the server log file');
|
|
119
|
+
|
|
120
|
+
if (typeof config.uploadFileSizeLimit === 'number') {
|
|
121
|
+
const limit = config.uploadFileSizeLimit;
|
|
122
|
+
const mb = (limit / (1024 * 1024)).toFixed(2);
|
|
123
|
+
html += renderStatRow('Upload Size Limit', `${mb} MB (${limit.toLocaleString()} bytes)`, true, 'text-accent', 'Maximum allowed size for file uploads');
|
|
124
|
+
} else {
|
|
125
|
+
html += renderStatRow('Upload Size Limit', 'No limit', false, 'text-accent', 'Maximum allowed size for file uploads');
|
|
126
|
+
}
|
|
127
|
+
html += '</div>';
|
|
128
|
+
|
|
129
|
+
// PL/SQL Routes
|
|
130
|
+
html += renderSectionTitle('PL/SQL Routes', 'database');
|
|
131
|
+
if (config.routePlSql?.length) {
|
|
132
|
+
html += config.routePlSql.map((r, i) => renderPlSqlRoute(r, i)).join('');
|
|
133
|
+
} else {
|
|
134
|
+
html += '<div class="empty-state">No PL/SQL routes configured</div>';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Static Routes
|
|
138
|
+
html += renderSectionTitle('Static Routes', 'folder_shared');
|
|
139
|
+
if (config.routeStatic?.length) {
|
|
140
|
+
html += config.routeStatic.map((r, i) => renderStaticRoute(r, i)).join('');
|
|
141
|
+
} else {
|
|
142
|
+
html += '<div class="empty-state">No static routes configured</div>';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
html += '</div>';
|
|
146
|
+
return html;
|
|
147
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {ErrorLogResponse} from '../schemas.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error log row template.
|
|
5
|
+
*
|
|
6
|
+
* @param l - The error log entry.
|
|
7
|
+
* @returns HTML string for the error row.
|
|
8
|
+
*/
|
|
9
|
+
export function errorRow(l: ErrorLogResponse): string {
|
|
10
|
+
return `
|
|
11
|
+
<tr title="Click to see full error details">
|
|
12
|
+
<td title="Timestamp: ${new Date(l.timestamp).toLocaleString()}">${new Date(l.timestamp).toLocaleString()}</td>
|
|
13
|
+
<td title="Method: ${l.req?.method ?? '-'}"><code>${l.req?.method ?? '-'}</code></td>
|
|
14
|
+
<td title="URL: ${l.req?.url ?? '-'}">${l.req?.url ?? '-'}</td>
|
|
15
|
+
<td title="Click for details: ${l.details?.fullMessage ?? ''}" class="break-all">${l.message}</td>
|
|
16
|
+
<td style="width: 40px; text-align: center; color: var(--accent); opacity: 0.7">
|
|
17
|
+
<span class="material-symbols-rounded">chevron_right</span>
|
|
18
|
+
</td>
|
|
19
|
+
</tr>
|
|
20
|
+
`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {StatusResponse} from '../schemas.js';
|
|
2
|
+
|
|
3
|
+
type PoolInfo = StatusResponse['pools'][number];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pool card template.
|
|
7
|
+
*
|
|
8
|
+
* @param p - The pool data.
|
|
9
|
+
* @returns HTML string for the pool card.
|
|
10
|
+
*/
|
|
11
|
+
export function poolCard(p: PoolInfo): string {
|
|
12
|
+
const utilization = p.connectionsOpen > 0 ? Math.round((p.connectionsInUse / p.connectionsOpen) * 100) : 0;
|
|
13
|
+
const utilColor = utilization > 90 ? 'var(--danger)' : utilization > 70 ? 'var(--warning)' : 'var(--success)';
|
|
14
|
+
|
|
15
|
+
const stats = p.stats ?? undefined;
|
|
16
|
+
|
|
17
|
+
return `
|
|
18
|
+
<div class="card card-clickable transition-all duration-200" title="Connection pool status for ${p.name}">
|
|
19
|
+
<div class="card-header">
|
|
20
|
+
<span class="material-symbols-rounded">database</span>
|
|
21
|
+
<h3 class="text-lg font-bold text-bright">${p.name}</h3>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="mb-6" title="Utilization: ${utilization}% of open connections are currently in use">
|
|
24
|
+
<div class="flex justify-between items-baseline mb-2">
|
|
25
|
+
<span class="text-3xl font-bold text-bright">${utilization}%</span>
|
|
26
|
+
<span class="text-main font-mono">${p.connectionsInUse} / ${p.connectionsOpen}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="progress-bar">
|
|
29
|
+
<div class="progress-bar-fill transition-all duration-300" style="width: ${utilization}%; background: ${utilColor}"></div>
|
|
30
|
+
</div>
|
|
31
|
+
<small class="description mt-2 block opacity-75">Pool Utilization (Active / Open)</small>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="stat-row text-sm" title="Total number of connections currently open in the pool">
|
|
35
|
+
<span class="text-xs uppercase font-bold opacity-75">Connections Open</span>
|
|
36
|
+
<span class="font-mono text-accent font-bold">${p.connectionsOpen}</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="stat-row text-sm" title="Number of connections currently active and handling requests">
|
|
39
|
+
<span class="text-xs uppercase font-bold opacity-75">Connections In Use</span>
|
|
40
|
+
<span class="font-mono text-accent font-bold">${p.connectionsInUse}</span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
${
|
|
44
|
+
stats
|
|
45
|
+
? `
|
|
46
|
+
<div class="mt-4 pt-4 border-t">
|
|
47
|
+
<div class="stat-row text-sm" title="Cumulative number of requests handled by this pool since server start">
|
|
48
|
+
<span>Total Requests</span>
|
|
49
|
+
<span class="font-mono text-accent font-bold">${stats.totalRequests.toLocaleString()}</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="stat-row text-sm" title="Number of requests that timed out waiting for a connection">
|
|
52
|
+
<span>Total Timeouts</span>
|
|
53
|
+
<span class="font-mono ${stats.totalTimeouts > 0 ? 'text-danger' : 'text-accent'} font-bold">${stats.totalTimeouts.toLocaleString()}</span>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
`
|
|
57
|
+
: ''
|
|
58
|
+
}
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {TraceEntry} from '../types.js';
|
|
2
|
+
import {formatMs} from '../util/format.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render a single trace row for the trace table.
|
|
6
|
+
*
|
|
7
|
+
* @param trace - The trace log entry.
|
|
8
|
+
* @returns HTML string.
|
|
9
|
+
*/
|
|
10
|
+
export function traceRow(trace: TraceEntry): string {
|
|
11
|
+
const time = new Date(trace.timestamp).toLocaleTimeString();
|
|
12
|
+
const statusClass = trace.status === 'success' ? 'text-success' : 'text-danger';
|
|
13
|
+
|
|
14
|
+
return `
|
|
15
|
+
<tr class="trace-table-row cursor-pointer hover:bg-white/5 transition-colors border-b border-white/5" data-id="${trace.id}">
|
|
16
|
+
<td class="px-4 py-2 font-mono text-xs text-dim">${time}</td>
|
|
17
|
+
<td class="px-4 py-2 text-dim text-xs truncate max-w-[150px]" title="${trace.source}">${trace.source}</td>
|
|
18
|
+
<td class="px-4 py-2 text-bright font-mono text-xs truncate max-w-[300px]" title="${trace.url}">${trace.url}</td>
|
|
19
|
+
<td class="px-4 py-2 font-bold ${statusClass} text-xs uppercase">${trace.status}</td>
|
|
20
|
+
<td class="px-4 py-2 text-right font-mono text-xs text-dim">${formatMs(trace.duration)}</td>
|
|
21
|
+
<td class="px-4 py-2 text-dim text-xs truncate max-w-[200px]" title="${trace.procedure ?? ''}">${trace.procedure ?? '-'}</td>
|
|
22
|
+
</tr>
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"noEmit": true,
|
|
4
|
+
"target": "es2020",
|
|
5
|
+
"module": "es2020",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"checkJs": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"strictNullChecks": true,
|
|
15
|
+
"strictFunctionTypes": true,
|
|
16
|
+
"strictBindCallApply": true,
|
|
17
|
+
"noImplicitThis": true,
|
|
18
|
+
"useUnknownInCatchVariables": true,
|
|
19
|
+
"alwaysStrict": true,
|
|
20
|
+
"lib": ["es2020", "dom", "dom.iterable"]
|
|
21
|
+
},
|
|
22
|
+
"include": ["**/*.js", "**/*.ts", "**/*.d.ts"],
|
|
23
|
+
"exclude": ["lib/**"]
|
|
24
|
+
}
|