web_plsql 1.0.0 → 1.3.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 +108 -13
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/frontend/assets/main-0oxRTIkL.js +147 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.br +0 -0
- package/dist/frontend/assets/main-0oxRTIkL.js.gz +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css +1 -0
- package/dist/frontend/assets/main-TYgLak6O.css.br +0 -0
- package/dist/frontend/assets/main-TYgLak6O.css.gz +0 -0
- package/dist/frontend/index.html +516 -0
- 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 +3548 -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 +45 -32
- package/examples/config-native.js +0 -68
- package/src/admin/client/charts.ts +0 -299
- package/src/admin/client/main.js +0 -3
- package/src/admin/client/tailwind.css +0 -41
- package/src/admin/index.html +0 -315
- package/src/admin/js/api.ts +0 -95
- package/src/admin/js/app.ts +0 -306
- package/src/admin/js/eslint.config.js +0 -74
- package/src/admin/js/schemas.ts +0 -153
- package/src/admin/js/templates/config.ts +0 -146
- package/src/admin/js/templates/errorRow.ts +0 -18
- package/src/admin/js/templates/index.ts +0 -3
- package/src/admin/js/templates/poolCard.ts +0 -61
- package/src/admin/js/tsconfig.json +0 -24
- package/src/admin/js/types.ts +0 -223
- package/src/admin/js/ui/theme.ts +0 -93
- package/src/admin/js/ui/views.ts +0 -164
- package/src/admin/js/util/format.ts +0 -27
- package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
- package/src/admin/lib/chart.bundle.js +0 -139
- package/src/admin/style.css +0 -1321
- package/src/bin/load-test.js +0 -202
- package/src/handler/handlerAdmin.js +0 -198
- package/src/handler/handlerLogger.js +0 -21
- package/src/handler/handlerMetrics.js +0 -68
- package/src/handler/handlerUpload.js +0 -26
- package/src/handler/plsql/cgi.js +0 -131
- package/src/handler/plsql/errorPage.js +0 -117
- package/src/handler/plsql/handlerPlSql.js +0 -90
- package/src/handler/plsql/parsePage.js +0 -213
- package/src/handler/plsql/procedure.js +0 -356
- package/src/handler/plsql/procedureError.js +0 -40
- package/src/handler/plsql/procedureNamed.js +0 -268
- package/src/handler/plsql/procedureSanitize.js +0 -262
- package/src/handler/plsql/procedureVariable.js +0 -54
- package/src/handler/plsql/request.js +0 -124
- package/src/handler/plsql/requestError.js +0 -23
- package/src/handler/plsql/sendResponse.js +0 -136
- package/src/handler/plsql/stream.js +0 -28
- package/src/handler/plsql/upload.js +0 -109
- package/src/index.js +0 -20
- package/src/server/config.js +0 -57
- package/src/server/connections.js +0 -40
- package/src/server/server.js +0 -289
- 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/trace.js +0 -325
- package/src/util/type.js +0 -6
- package/src/util/util.js +0 -66
- package/src/version.js +0 -5
- package/types/handler/handlerAdmin.d.ts +0 -9
- package/types/handler/handlerLogger.d.ts +0 -2
- package/types/handler/handlerMetrics.d.ts +0 -25
- 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/parsePage.d.ts +0 -3
- package/types/handler/plsql/procedure.d.ts +0 -12
- 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 -11
- package/types/server/config.d.ts +0 -2
- package/types/server/server.d.ts +0 -74
- 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/trace.d.ts +0 -22
- 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/admin/js/app.ts
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
import {typedApi} from './api.js';
|
|
2
|
-
import {initCharts, updateCharts} from '../client/charts.js';
|
|
3
|
-
import {formatDuration, formatDateTime} from './util/format.js';
|
|
4
|
-
import {initTheme} from './ui/theme.js';
|
|
5
|
-
import {refreshErrors, refreshAccess, refreshConfig, refreshPools, refreshSystem} from './ui/views.js';
|
|
6
|
-
import type {State} from './types.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* View icons mapping.
|
|
10
|
-
*/
|
|
11
|
-
const viewIcons: Record<string, string> = {
|
|
12
|
-
overview: 'dashboard',
|
|
13
|
-
errors: 'error',
|
|
14
|
-
access: 'list_alt',
|
|
15
|
-
cache: 'cached',
|
|
16
|
-
pools: 'database',
|
|
17
|
-
config: 'settings',
|
|
18
|
-
system: 'terminal',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Application state.
|
|
23
|
-
*/
|
|
24
|
-
const state: State = {
|
|
25
|
-
currentView: 'overview',
|
|
26
|
-
status: {},
|
|
27
|
-
maxHistoryPoints: 30,
|
|
28
|
-
lastRequestCount: 0,
|
|
29
|
-
lastErrorCount: 0,
|
|
30
|
-
lastUpdateTime: Date.now(),
|
|
31
|
-
refreshTimer: null,
|
|
32
|
-
history: {
|
|
33
|
-
labels: [],
|
|
34
|
-
requests: [],
|
|
35
|
-
avgResponseTimes: [],
|
|
36
|
-
poolUsage: {},
|
|
37
|
-
},
|
|
38
|
-
charts: {},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Clear cache for a pool.
|
|
43
|
-
*
|
|
44
|
-
* @param poolName - The pool name.
|
|
45
|
-
*/
|
|
46
|
-
export async function clearCache(poolName: string): Promise<void> {
|
|
47
|
-
await typedApi.post('api/cache/clear', {poolName});
|
|
48
|
-
await updateStatus();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Update server status.
|
|
53
|
-
*/
|
|
54
|
-
async function updateStatus(): Promise<void> {
|
|
55
|
-
const now = Date.now();
|
|
56
|
-
const newStatus = await typedApi.getStatus();
|
|
57
|
-
state.status = newStatus;
|
|
58
|
-
|
|
59
|
-
if (!newStatus.metrics || !newStatus.pools) return;
|
|
60
|
-
|
|
61
|
-
const deltaSec = (now - state.lastUpdateTime) / 1000;
|
|
62
|
-
const reqCountDelta = newStatus.metrics.requestCount - state.lastRequestCount;
|
|
63
|
-
|
|
64
|
-
const reqPerSec = deltaSec > 0 ? reqCountDelta / deltaSec : 0;
|
|
65
|
-
const avgResponseTime = newStatus.metrics.avgResponseTime;
|
|
66
|
-
|
|
67
|
-
state.lastRequestCount = newStatus.metrics.requestCount;
|
|
68
|
-
state.lastErrorCount = newStatus.metrics.errorCount;
|
|
69
|
-
state.lastUpdateTime = now;
|
|
70
|
-
|
|
71
|
-
const timeLabel = new Date().toLocaleTimeString();
|
|
72
|
-
updateCharts(state, timeLabel, reqPerSec, avgResponseTime, newStatus.pools);
|
|
73
|
-
|
|
74
|
-
const sidebarVersion = document.getElementById('sidebar-version');
|
|
75
|
-
if (sidebarVersion) sidebarVersion.textContent = `v${newStatus.version}`;
|
|
76
|
-
|
|
77
|
-
const uptimeVal = document.getElementById('uptime-val');
|
|
78
|
-
if (uptimeVal) uptimeVal.textContent = formatDuration(newStatus.uptime);
|
|
79
|
-
|
|
80
|
-
const startTimeVal = document.getElementById('start-time-val');
|
|
81
|
-
if (startTimeVal) startTimeVal.textContent = `Started: ${formatDateTime(newStatus.startTime)}`;
|
|
82
|
-
|
|
83
|
-
const reqCount = document.getElementById('req-count');
|
|
84
|
-
if (reqCount) reqCount.textContent = newStatus.metrics.requestCount.toLocaleString();
|
|
85
|
-
|
|
86
|
-
const reqPerSecVal = document.getElementById('req-per-sec');
|
|
87
|
-
if (reqPerSecVal) reqPerSecVal.textContent = `${reqPerSec.toFixed(2)} req/s`;
|
|
88
|
-
|
|
89
|
-
const avgRespTimeVal = document.getElementById('avg-resp-time');
|
|
90
|
-
if (avgRespTimeVal) avgRespTimeVal.textContent = `Avg: ${avgResponseTime.toFixed(1)}ms`;
|
|
91
|
-
|
|
92
|
-
const errCount = document.getElementById('err-count');
|
|
93
|
-
if (errCount) errCount.textContent = newStatus.metrics.errorCount.toLocaleString();
|
|
94
|
-
|
|
95
|
-
// Update Cache Overview
|
|
96
|
-
const caches = await typedApi.getCache();
|
|
97
|
-
let totalHits = 0;
|
|
98
|
-
let totalMisses = 0;
|
|
99
|
-
caches.forEach((c) => {
|
|
100
|
-
totalHits += c.procedureNameCache.stats.hits + c.argumentCache.stats.hits;
|
|
101
|
-
totalMisses += c.procedureNameCache.stats.misses + c.argumentCache.stats.misses;
|
|
102
|
-
});
|
|
103
|
-
const totalRequests = totalHits + totalMisses;
|
|
104
|
-
const hitRate = totalRequests > 0 ? Math.round((totalHits / totalRequests) * 100) : 0;
|
|
105
|
-
|
|
106
|
-
const cacheHitRateVal = document.getElementById('cache-hit-rate-val');
|
|
107
|
-
if (cacheHitRateVal) {
|
|
108
|
-
cacheHitRateVal.textContent = `${hitRate}%`;
|
|
109
|
-
cacheHitRateVal.style.color = hitRate > 80 ? 'var(--success)' : hitRate > 50 ? 'var(--warning)' : 'var(--danger)';
|
|
110
|
-
}
|
|
111
|
-
const cacheHitsVal = document.getElementById('cache-hits-val');
|
|
112
|
-
if (cacheHitsVal) cacheHitsVal.textContent = totalHits.toLocaleString();
|
|
113
|
-
const cacheMissesVal = document.getElementById('cache-misses-val');
|
|
114
|
-
if (cacheMissesVal) cacheMissesVal.textContent = totalMisses.toLocaleString();
|
|
115
|
-
|
|
116
|
-
const dot = document.getElementById('server-status-dot');
|
|
117
|
-
const text = document.getElementById('server-status-text');
|
|
118
|
-
if (dot) dot.className = 'dot ' + newStatus.status;
|
|
119
|
-
if (text) text.textContent = newStatus.status.toUpperCase();
|
|
120
|
-
|
|
121
|
-
const btnPause = document.getElementById('btn-pause');
|
|
122
|
-
if (btnPause) btnPause.style.display = newStatus.status === 'running' ? 'flex' : 'none';
|
|
123
|
-
|
|
124
|
-
const btnResume = document.getElementById('btn-resume');
|
|
125
|
-
if (btnResume) btnResume.style.display = newStatus.status === 'paused' ? 'flex' : 'none';
|
|
126
|
-
|
|
127
|
-
if (state.currentView === 'errors') await refreshErrors();
|
|
128
|
-
if (state.currentView === 'access') await refreshAccess();
|
|
129
|
-
if (state.currentView === 'pools') refreshPools(newStatus);
|
|
130
|
-
if (state.currentView === 'config') refreshConfig(state);
|
|
131
|
-
if (state.currentView === 'system') refreshSystem(newStatus);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Start the refresh timer.
|
|
136
|
-
*/
|
|
137
|
-
function startRefreshTimer(): void {
|
|
138
|
-
stopRefreshTimer();
|
|
139
|
-
const checkbox = document.getElementById('auto-refresh-toggle') as HTMLInputElement | null;
|
|
140
|
-
const intervalSelect = document.getElementById('refresh-interval') as HTMLSelectElement | null;
|
|
141
|
-
if (checkbox?.checked && intervalSelect) {
|
|
142
|
-
const interval = parseInt(intervalSelect.value);
|
|
143
|
-
state.refreshTimer = setInterval(() => {
|
|
144
|
-
void updateStatus();
|
|
145
|
-
}, interval);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Stop the refresh timer.
|
|
151
|
-
*/
|
|
152
|
-
function stopRefreshTimer(): void {
|
|
153
|
-
if (state.refreshTimer) {
|
|
154
|
-
clearInterval(state.refreshTimer);
|
|
155
|
-
state.refreshTimer = null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Update history points labels to be time-based.
|
|
161
|
-
*/
|
|
162
|
-
function updateHistoryLabels(): void {
|
|
163
|
-
const intervalSelect = document.getElementById('refresh-interval') as HTMLSelectElement | null;
|
|
164
|
-
const historySelect = document.getElementById('chart-history-points') as HTMLSelectElement | null;
|
|
165
|
-
if (!intervalSelect || !historySelect) return;
|
|
166
|
-
|
|
167
|
-
const intervalMs = parseInt(intervalSelect.value);
|
|
168
|
-
const points = [30, 60, 120];
|
|
169
|
-
|
|
170
|
-
points.forEach((pts, idx) => {
|
|
171
|
-
const option = historySelect.options[idx];
|
|
172
|
-
if (!option) return;
|
|
173
|
-
|
|
174
|
-
const totalSeconds = (pts * intervalMs) / 1000;
|
|
175
|
-
let timeStr = '';
|
|
176
|
-
if (totalSeconds < 60) {
|
|
177
|
-
timeStr = `${totalSeconds}s`;
|
|
178
|
-
} else if (totalSeconds < 3600) {
|
|
179
|
-
timeStr = `${Math.round(totalSeconds / 60)} min`;
|
|
180
|
-
} else {
|
|
181
|
-
const hours = totalSeconds / 3600;
|
|
182
|
-
timeStr = hours === Math.round(hours) ? `${hours} hours` : `${hours.toFixed(1)} hours`;
|
|
183
|
-
}
|
|
184
|
-
option.textContent = `Last ${timeStr}`;
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Navigation
|
|
189
|
-
document.querySelectorAll('nav button').forEach((btnEl) => {
|
|
190
|
-
const btn = btnEl as HTMLButtonElement;
|
|
191
|
-
btn.onclick = async () => {
|
|
192
|
-
const view = btn.dataset.view;
|
|
193
|
-
if (!view) return;
|
|
194
|
-
state.currentView = view;
|
|
195
|
-
document.querySelectorAll('.view').forEach((vEl) => {
|
|
196
|
-
const v = vEl as HTMLElement;
|
|
197
|
-
v.style.display = 'none';
|
|
198
|
-
});
|
|
199
|
-
const viewEl = document.getElementById('view-' + view);
|
|
200
|
-
if (viewEl) viewEl.style.display = 'block';
|
|
201
|
-
document.querySelectorAll('nav button').forEach((b) => b.classList.remove('active'));
|
|
202
|
-
btn.classList.add('active');
|
|
203
|
-
const viewTitleEl = document.getElementById('view-title');
|
|
204
|
-
if (viewTitleEl) {
|
|
205
|
-
// Get text content excluding the icon span
|
|
206
|
-
const textNodes = Array.from(btn.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE);
|
|
207
|
-
const btnText = textNodes.map((node) => node.textContent?.trim()).join('');
|
|
208
|
-
const icon = viewIcons[view] || 'chevron_right';
|
|
209
|
-
viewTitleEl.innerHTML = `<span class="material-symbols-rounded">${icon}</span>${btnText}`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (view === 'errors') await refreshErrors();
|
|
213
|
-
if (view === 'access') await refreshAccess();
|
|
214
|
-
if (view === 'pools') refreshPools(state.status);
|
|
215
|
-
if (view === 'config') refreshConfig(state);
|
|
216
|
-
if (view === 'system') refreshSystem(state.status);
|
|
217
|
-
};
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const autoRefreshToggle = document.getElementById('auto-refresh-toggle') as HTMLInputElement | null;
|
|
221
|
-
if (autoRefreshToggle) {
|
|
222
|
-
autoRefreshToggle.onchange = (e: Event) => {
|
|
223
|
-
const target = e.target as HTMLInputElement;
|
|
224
|
-
if (target.checked) startRefreshTimer();
|
|
225
|
-
else stopRefreshTimer();
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const refreshIntervalSelect = document.getElementById('refresh-interval') as HTMLSelectElement | null;
|
|
230
|
-
if (refreshIntervalSelect) {
|
|
231
|
-
refreshIntervalSelect.onchange = () => {
|
|
232
|
-
startRefreshTimer();
|
|
233
|
-
updateHistoryLabels();
|
|
234
|
-
refreshSystem(state.status);
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const chartHistorySelect = document.getElementById('chart-history-points') as HTMLSelectElement | null;
|
|
239
|
-
if (chartHistorySelect) {
|
|
240
|
-
chartHistorySelect.onchange = () => {
|
|
241
|
-
state.maxHistoryPoints = parseInt(chartHistorySelect.value);
|
|
242
|
-
// Trim history if needed
|
|
243
|
-
while (state.history.labels.length > state.maxHistoryPoints) {
|
|
244
|
-
state.history.labels.shift();
|
|
245
|
-
state.history.requests.shift();
|
|
246
|
-
state.history.avgResponseTimes.shift();
|
|
247
|
-
Object.values(state.history.poolUsage).forEach((u) => u.shift());
|
|
248
|
-
}
|
|
249
|
-
void updateStatus();
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const manualRefreshBtn = document.getElementById('manual-refresh');
|
|
254
|
-
if (manualRefreshBtn) {
|
|
255
|
-
manualRefreshBtn.onclick = () => {
|
|
256
|
-
void updateStatus();
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Server controls
|
|
261
|
-
const btnPause = document.getElementById('btn-pause');
|
|
262
|
-
if (btnPause) {
|
|
263
|
-
btnPause.onclick = () => {
|
|
264
|
-
void typedApi.post('api/server/pause').then(() => {
|
|
265
|
-
void updateStatus();
|
|
266
|
-
});
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const btnResume = document.getElementById('btn-resume');
|
|
271
|
-
if (btnResume) {
|
|
272
|
-
btnResume.onclick = () => {
|
|
273
|
-
void typedApi.post('api/server/resume').then(() => {
|
|
274
|
-
void updateStatus();
|
|
275
|
-
});
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const btnStop = document.getElementById('btn-stop');
|
|
280
|
-
if (btnStop) {
|
|
281
|
-
btnStop.onclick = () => {
|
|
282
|
-
if (confirm('Stop the server?')) {
|
|
283
|
-
void typedApi.post('api/server/stop');
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const btnClearAllCache = document.getElementById('btn-clear-all-cache');
|
|
289
|
-
if (btnClearAllCache) {
|
|
290
|
-
btnClearAllCache.onclick = async () => {
|
|
291
|
-
if (confirm('Are you sure you want to clear all caches in all pools? This action cannot be undone.')) {
|
|
292
|
-
await typedApi.post('api/cache/clear', {});
|
|
293
|
-
await updateStatus();
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Initialize
|
|
299
|
-
initTheme(state);
|
|
300
|
-
initCharts(state);
|
|
301
|
-
updateHistoryLabels();
|
|
302
|
-
void updateStatus()
|
|
303
|
-
.then(() => {
|
|
304
|
-
startRefreshTimer();
|
|
305
|
-
})
|
|
306
|
-
.catch(console.error);
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import eslint from '@eslint/js';
|
|
4
|
-
import {defineConfig} from 'eslint/config';
|
|
5
|
-
import tseslint from 'typescript-eslint';
|
|
6
|
-
import globals from 'globals';
|
|
7
|
-
|
|
8
|
-
export default defineConfig([
|
|
9
|
-
{
|
|
10
|
-
ignores: ['**/.*', 'lib/**', 'node_modules/**', '**/lib/**'],
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
{
|
|
14
|
-
linterOptions: {
|
|
15
|
-
reportUnusedDisableDirectives: 'error',
|
|
16
|
-
reportUnusedInlineConfigs: 'error',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
eslint.configs.recommended,
|
|
21
|
-
...tseslint.configs.strictTypeChecked,
|
|
22
|
-
...tseslint.configs.stylisticTypeChecked,
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
languageOptions: {
|
|
26
|
-
globals: {
|
|
27
|
-
...globals.browser,
|
|
28
|
-
},
|
|
29
|
-
parserOptions: {
|
|
30
|
-
projectService: true,
|
|
31
|
-
tsconfigRootDir: import.meta.dirname,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
rules: {
|
|
35
|
-
// Allow empty functions for chart update stubs
|
|
36
|
-
'@typescript-eslint/no-empty-function': 'off',
|
|
37
|
-
|
|
38
|
-
// Disable JSDoc requirements in TypeScript files
|
|
39
|
-
'jsdoc/require-param': 'off',
|
|
40
|
-
'jsdoc/require-param-type': 'off',
|
|
41
|
-
'jsdoc/require-returns-type': 'off',
|
|
42
|
-
'jsdoc/require-param-description': 'off',
|
|
43
|
-
'jsdoc/require-property-description': 'off',
|
|
44
|
-
'jsdoc/require-returns-description': 'off',
|
|
45
|
-
|
|
46
|
-
// Allow toString() for logging/debugging
|
|
47
|
-
'@typescript-eslint/no-base-to-string': 'off',
|
|
48
|
-
|
|
49
|
-
// Relaxed rules for template literals
|
|
50
|
-
'@typescript-eslint/restrict-template-expressions': 'off',
|
|
51
|
-
|
|
52
|
-
// Consistent type definitions
|
|
53
|
-
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
54
|
-
|
|
55
|
-
// Unused vars
|
|
56
|
-
'@typescript-eslint/no-unused-vars': [
|
|
57
|
-
'warn',
|
|
58
|
-
{
|
|
59
|
-
caughtErrors: 'none',
|
|
60
|
-
argsIgnorePattern: '^_',
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
|
|
64
|
-
// Deprecation warnings
|
|
65
|
-
'@typescript-eslint/no-deprecated': 'warn',
|
|
66
|
-
|
|
67
|
-
// Void expression
|
|
68
|
-
'@typescript-eslint/no-confusing-void-expression': 'off',
|
|
69
|
-
|
|
70
|
-
// Unnecessary conditions
|
|
71
|
-
'@typescript-eslint/no-unnecessary-condition': 'off',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
]);
|
package/src/admin/js/schemas.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import {z} from 'zod';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cache statistics schema.
|
|
5
|
-
*/
|
|
6
|
-
export const cacheStatsSchema = z.object({
|
|
7
|
-
hits: z.number(),
|
|
8
|
-
misses: z.number(),
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Cache information schema.
|
|
13
|
-
*/
|
|
14
|
-
export const cacheInfoSchema = z.object({
|
|
15
|
-
size: z.number(),
|
|
16
|
-
stats: cacheStatsSchema,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Cache data schema for a connection pool.
|
|
21
|
-
*/
|
|
22
|
-
export const cacheDataSchema = z.object({
|
|
23
|
-
poolName: z.string(),
|
|
24
|
-
procedureNameCache: cacheInfoSchema,
|
|
25
|
-
argumentCache: cacheInfoSchema,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Pool statistics schema.
|
|
30
|
-
*/
|
|
31
|
-
export const poolStatsSchema = z.object({
|
|
32
|
-
totalRequests: z.number(),
|
|
33
|
-
totalTimeouts: z.number(),
|
|
34
|
-
totalRequestsEnqueued: z.number().optional(),
|
|
35
|
-
totalRequestsDequeued: z.number().optional(),
|
|
36
|
-
totalRequestsFailed: z.number().optional(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Pool information schema.
|
|
41
|
-
*/
|
|
42
|
-
export const poolInfoSchema = z.object({
|
|
43
|
-
name: z.string(),
|
|
44
|
-
connectionsInUse: z.number(),
|
|
45
|
-
connectionsOpen: z.number(),
|
|
46
|
-
stats: poolStatsSchema.nullable(),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Server metrics schema.
|
|
51
|
-
*/
|
|
52
|
-
export const metricsSchema = z.object({
|
|
53
|
-
requestCount: z.number(),
|
|
54
|
-
errorCount: z.number(),
|
|
55
|
-
avgResponseTime: z.number(),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Route configuration schema.
|
|
60
|
-
*/
|
|
61
|
-
export const routeConfigSchema = z.object({
|
|
62
|
-
route: z.string(),
|
|
63
|
-
// PL/SQL specific
|
|
64
|
-
user: z.string().optional(),
|
|
65
|
-
password: z.string().optional(),
|
|
66
|
-
connectString: z.string().optional(),
|
|
67
|
-
defaultPage: z.string().optional(),
|
|
68
|
-
pathAlias: z.string().optional(),
|
|
69
|
-
pathAliasProcedure: z.string().optional(),
|
|
70
|
-
documentTable: z.string().optional(),
|
|
71
|
-
exclusionList: z.array(z.string()).optional(),
|
|
72
|
-
requestValidationFunction: z.string().optional(),
|
|
73
|
-
transactionMode: z.unknown().optional(),
|
|
74
|
-
errorStyle: z.string().optional(),
|
|
75
|
-
// Static specific
|
|
76
|
-
directoryPath: z.string().optional(),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Server configuration schema.
|
|
81
|
-
*/
|
|
82
|
-
export const serverConfigSchema = z.object({
|
|
83
|
-
port: z.number(),
|
|
84
|
-
adminRoute: z.string().optional(),
|
|
85
|
-
adminUser: z.string().optional(),
|
|
86
|
-
adminPassword: z.string().optional(),
|
|
87
|
-
loggerFilename: z.string(),
|
|
88
|
-
uploadFileSizeLimit: z.number().optional(),
|
|
89
|
-
routePlSql: z.array(routeConfigSchema),
|
|
90
|
-
routeStatic: z.array(routeConfigSchema),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* System information schema.
|
|
95
|
-
*/
|
|
96
|
-
export const systemInfoSchema = z.object({
|
|
97
|
-
nodeVersion: z.string(),
|
|
98
|
-
platform: z.string(),
|
|
99
|
-
arch: z.string(),
|
|
100
|
-
memory: z.object({
|
|
101
|
-
rss: z.number(),
|
|
102
|
-
heapTotal: z.number(),
|
|
103
|
-
heapUsed: z.number(),
|
|
104
|
-
external: z.number(),
|
|
105
|
-
}),
|
|
106
|
-
cpu: z.object({
|
|
107
|
-
user: z.number(),
|
|
108
|
-
system: z.number(),
|
|
109
|
-
}),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Server status response schema.
|
|
114
|
-
*/
|
|
115
|
-
export const statusSchema = z.object({
|
|
116
|
-
version: z.string(),
|
|
117
|
-
status: z.enum(['running', 'paused', 'stopped']),
|
|
118
|
-
uptime: z.number(),
|
|
119
|
-
startTime: z.string(),
|
|
120
|
-
metrics: metricsSchema,
|
|
121
|
-
pools: z.array(poolInfoSchema),
|
|
122
|
-
system: systemInfoSchema,
|
|
123
|
-
config: serverConfigSchema.partial(),
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Error log entry schema.
|
|
128
|
-
*/
|
|
129
|
-
export const errorLogSchema = z.object({
|
|
130
|
-
timestamp: z.string(),
|
|
131
|
-
req: z
|
|
132
|
-
.object({
|
|
133
|
-
method: z.string().optional(),
|
|
134
|
-
url: z.string().optional(),
|
|
135
|
-
})
|
|
136
|
-
.optional(),
|
|
137
|
-
message: z.string(),
|
|
138
|
-
details: z
|
|
139
|
-
.object({
|
|
140
|
-
fullMessage: z.string().optional(),
|
|
141
|
-
})
|
|
142
|
-
.optional(),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
export const accessLogResponseSchema = z.union([z.array(z.string()), z.object({message: z.string()})]);
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* API response type helpers.
|
|
149
|
-
*/
|
|
150
|
-
export type StatusResponse = z.infer<typeof statusSchema>;
|
|
151
|
-
export type CacheDataResponse = z.infer<typeof cacheDataSchema>;
|
|
152
|
-
export type ErrorLogResponse = z.infer<typeof errorLogSchema>;
|
|
153
|
-
export type AccessLogResponse = z.infer<typeof accessLogResponseSchema>;
|
|
@@ -1,146 +0,0 @@
|
|
|
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
|
-
* @returns HTML string.
|
|
10
|
-
*/
|
|
11
|
-
function renderStatRow(label: string, value: string | number, isMono = true, colorClass = 'text-accent'): string {
|
|
12
|
-
const valClass = `${isMono ? 'font-mono' : ''} ${colorClass} font-bold break-all`.trim();
|
|
13
|
-
return `
|
|
14
|
-
<div class="stat-row text-sm">
|
|
15
|
-
<span>${label}</span>
|
|
16
|
-
<span class="${valClass}">${value}</span>
|
|
17
|
-
</div>
|
|
18
|
-
`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Render a configuration section header.
|
|
23
|
-
* @param title - The section title.
|
|
24
|
-
* @param icon - The Material icon name.
|
|
25
|
-
* @returns HTML string.
|
|
26
|
-
*/
|
|
27
|
-
function renderSectionTitle(title: string, icon: string): string {
|
|
28
|
-
return `
|
|
29
|
-
<div class="card-header mt-6 mb-2">
|
|
30
|
-
<span class="material-symbols-rounded icon-sm">${icon}</span>
|
|
31
|
-
<h3 class="text-xs uppercase tracking-wider font-bold text-accent">${title}</h3>
|
|
32
|
-
</div>
|
|
33
|
-
`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Render a PL/SQL route card.
|
|
38
|
-
* @param r - The route configuration.
|
|
39
|
-
* @param index - The index of the route.
|
|
40
|
-
* @returns HTML string.
|
|
41
|
-
*/
|
|
42
|
-
function renderPlSqlRoute(r: RouteConfig, index: number): string {
|
|
43
|
-
const exclusionList = r.exclusionList?.length ? r.exclusionList.join(', ') : '(None)';
|
|
44
|
-
const transactionMode = typeof r.transactionMode === 'string' ? r.transactionMode : r.transactionMode ? 'Custom' : 'commit';
|
|
45
|
-
|
|
46
|
-
return `
|
|
47
|
-
<div class="card mb-6">
|
|
48
|
-
<div class="flex justify-between items-center mb-4">
|
|
49
|
-
<div class="flex items-center gap-3">
|
|
50
|
-
<span class="badge badge-info">PL/SQL Route #${index + 1}</span>
|
|
51
|
-
<code class="text-lg font-bold text-bright">${r.route}</code>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
56
|
-
<div>
|
|
57
|
-
<h4 class="text-xs font-bold text-main uppercase mb-3 opacity-75">Database Connection</h4>
|
|
58
|
-
${renderStatRow('Oracle User', r.user ?? '(Not set)')}
|
|
59
|
-
${renderStatRow('Connect String', r.connectString ?? '(Not set)')}
|
|
60
|
-
${renderStatRow('Password', r.password ?? '(None)')}
|
|
61
|
-
</div>
|
|
62
|
-
<div>
|
|
63
|
-
<h4 class="text-xs font-bold text-main uppercase mb-3 opacity-75">Request Handler</h4>
|
|
64
|
-
${renderStatRow('Default Page', r.defaultPage ?? 'index')}
|
|
65
|
-
${renderStatRow('Document Table', r.documentTable ?? '(None)')}
|
|
66
|
-
${renderStatRow('Error Style', r.errorStyle ?? 'table')}
|
|
67
|
-
${renderStatRow('Transaction Mode', transactionMode)}
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
<div class="mt-4 pt-4 border-t">
|
|
72
|
-
<h4 class="text-xs font-bold text-main uppercase mb-3 opacity-75">Path & Validation</h4>
|
|
73
|
-
${renderStatRow('Path Alias', r.pathAlias ?? '(None)')}
|
|
74
|
-
${renderStatRow('Path Alias Proc', r.pathAliasProcedure ?? '(None)')}
|
|
75
|
-
${renderStatRow('Exclusion List', exclusionList)}
|
|
76
|
-
${renderStatRow('Validation Func', r.requestValidationFunction ?? '(None)')}
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Render a Static route card.
|
|
84
|
-
* @param r - The route configuration.
|
|
85
|
-
* @param index - The index of the route.
|
|
86
|
-
* @returns HTML string.
|
|
87
|
-
*/
|
|
88
|
-
function renderStaticRoute(r: RouteConfig, index: number): string {
|
|
89
|
-
return `
|
|
90
|
-
<div class="card mb-4">
|
|
91
|
-
<div class="flex items-center gap-3 mb-4">
|
|
92
|
-
<span class="badge badge-success">Static Route #${index + 1}</span>
|
|
93
|
-
<code class="text-lg font-bold text-bright">${r.route}</code>
|
|
94
|
-
</div>
|
|
95
|
-
${renderStatRow('Directory Path', r.directoryPath ?? '(Not set)')}
|
|
96
|
-
</div>
|
|
97
|
-
`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Render the full server configuration.
|
|
102
|
-
* @param config - The server configuration.
|
|
103
|
-
* @returns HTML string.
|
|
104
|
-
*/
|
|
105
|
-
export function renderConfig(config: Partial<ServerConfig>): string {
|
|
106
|
-
let html = '<div class="config-view-content">';
|
|
107
|
-
|
|
108
|
-
// Global Server Section
|
|
109
|
-
html += '<div class="card mb-8">';
|
|
110
|
-
html += '<div class="card-header"><span class="material-symbols-rounded">dns</span><h3>Server Settings</h3></div>';
|
|
111
|
-
if (typeof config.port === 'number') {
|
|
112
|
-
html += renderStatRow('Port', config.port);
|
|
113
|
-
}
|
|
114
|
-
html += renderStatRow('Admin Route', config.adminRoute ?? '/admin');
|
|
115
|
-
html += renderStatRow('Admin User', config.adminUser ?? '(Not authenticated)');
|
|
116
|
-
html += renderStatRow('Admin Password', config.adminPassword ?? '(None)');
|
|
117
|
-
html += renderStatRow('Logger Filename', config.loggerFilename ?? '(Logging disabled)');
|
|
118
|
-
|
|
119
|
-
if (typeof config.uploadFileSizeLimit === 'number') {
|
|
120
|
-
const limit = config.uploadFileSizeLimit;
|
|
121
|
-
const mb = (limit / (1024 * 1024)).toFixed(2);
|
|
122
|
-
html += renderStatRow('Upload Size Limit', `${mb} MB (${limit.toLocaleString()} bytes)`);
|
|
123
|
-
} else {
|
|
124
|
-
html += renderStatRow('Upload Size Limit', 'No limit');
|
|
125
|
-
}
|
|
126
|
-
html += '</div>';
|
|
127
|
-
|
|
128
|
-
// PL/SQL Routes
|
|
129
|
-
html += renderSectionTitle('PL/SQL Routes', 'database');
|
|
130
|
-
if (config.routePlSql?.length) {
|
|
131
|
-
html += config.routePlSql.map((r, i) => renderPlSqlRoute(r, i)).join('');
|
|
132
|
-
} else {
|
|
133
|
-
html += '<div class="empty-state">No PL/SQL routes configured</div>';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Static Routes
|
|
137
|
-
html += renderSectionTitle('Static Routes', 'folder_shared');
|
|
138
|
-
if (config.routeStatic?.length) {
|
|
139
|
-
html += config.routeStatic.map((r, i) => renderStaticRoute(r, i)).join('');
|
|
140
|
-
} else {
|
|
141
|
-
html += '<div class="empty-state">No static routes configured</div>';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
html += '</div>';
|
|
145
|
-
return html;
|
|
146
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
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>
|
|
12
|
-
<td>${new Date(l.timestamp).toLocaleString()}</td>
|
|
13
|
-
<td><code>${l.req?.method ?? '-'}</code></td>
|
|
14
|
-
<td>${l.req?.url ?? '-'}</td>
|
|
15
|
-
<td title="${l.details?.fullMessage ?? ''}">${l.message}</td>
|
|
16
|
-
</tr>
|
|
17
|
-
`;
|
|
18
|
-
}
|