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.
Files changed (108) hide show
  1. package/README.md +108 -13
  2. package/dist/chunk-DQk6qfdC.mjs +18 -0
  3. package/dist/frontend/assets/main-0oxRTIkL.js +147 -0
  4. package/dist/frontend/assets/main-0oxRTIkL.js.br +0 -0
  5. package/dist/frontend/assets/main-0oxRTIkL.js.gz +0 -0
  6. package/dist/frontend/assets/main-TYgLak6O.css +1 -0
  7. package/dist/frontend/assets/main-TYgLak6O.css.br +0 -0
  8. package/dist/frontend/assets/main-TYgLak6O.css.gz +0 -0
  9. package/dist/frontend/index.html +516 -0
  10. package/dist/frontend/index.html.br +0 -0
  11. package/dist/frontend/index.html.gz +0 -0
  12. package/dist/index.d.mts +646 -0
  13. package/dist/index.mjs +3548 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/oracledb-mock-Dn8aHtd7.mjs +68 -0
  16. package/dist/oracledb-mock-Dn8aHtd7.mjs.map +1 -0
  17. package/examples/config-apex.js +1 -1
  18. package/examples/config-docker.js +1 -2
  19. package/package.json +45 -32
  20. package/examples/config-native.js +0 -68
  21. package/src/admin/client/charts.ts +0 -299
  22. package/src/admin/client/main.js +0 -3
  23. package/src/admin/client/tailwind.css +0 -41
  24. package/src/admin/index.html +0 -315
  25. package/src/admin/js/api.ts +0 -95
  26. package/src/admin/js/app.ts +0 -306
  27. package/src/admin/js/eslint.config.js +0 -74
  28. package/src/admin/js/schemas.ts +0 -153
  29. package/src/admin/js/templates/config.ts +0 -146
  30. package/src/admin/js/templates/errorRow.ts +0 -18
  31. package/src/admin/js/templates/index.ts +0 -3
  32. package/src/admin/js/templates/poolCard.ts +0 -61
  33. package/src/admin/js/tsconfig.json +0 -24
  34. package/src/admin/js/types.ts +0 -223
  35. package/src/admin/js/ui/theme.ts +0 -93
  36. package/src/admin/js/ui/views.ts +0 -164
  37. package/src/admin/js/util/format.ts +0 -27
  38. package/src/admin/lib/assets/main-zpdhQ1gD.css +0 -1
  39. package/src/admin/lib/chart.bundle.js +0 -139
  40. package/src/admin/style.css +0 -1321
  41. package/src/bin/load-test.js +0 -202
  42. package/src/handler/handlerAdmin.js +0 -198
  43. package/src/handler/handlerLogger.js +0 -21
  44. package/src/handler/handlerMetrics.js +0 -68
  45. package/src/handler/handlerUpload.js +0 -26
  46. package/src/handler/plsql/cgi.js +0 -131
  47. package/src/handler/plsql/errorPage.js +0 -117
  48. package/src/handler/plsql/handlerPlSql.js +0 -90
  49. package/src/handler/plsql/parsePage.js +0 -213
  50. package/src/handler/plsql/procedure.js +0 -356
  51. package/src/handler/plsql/procedureError.js +0 -40
  52. package/src/handler/plsql/procedureNamed.js +0 -268
  53. package/src/handler/plsql/procedureSanitize.js +0 -262
  54. package/src/handler/plsql/procedureVariable.js +0 -54
  55. package/src/handler/plsql/request.js +0 -124
  56. package/src/handler/plsql/requestError.js +0 -23
  57. package/src/handler/plsql/sendResponse.js +0 -136
  58. package/src/handler/plsql/stream.js +0 -28
  59. package/src/handler/plsql/upload.js +0 -109
  60. package/src/index.js +0 -20
  61. package/src/server/config.js +0 -57
  62. package/src/server/connections.js +0 -40
  63. package/src/server/server.js +0 -289
  64. package/src/types.js +0 -150
  65. package/src/util/cache.js +0 -123
  66. package/src/util/errorToString.js +0 -24
  67. package/src/util/file.js +0 -91
  68. package/src/util/html.js +0 -53
  69. package/src/util/jsonLogger.js +0 -47
  70. package/src/util/oracle.js +0 -80
  71. package/src/util/shutdown.js +0 -44
  72. package/src/util/trace.js +0 -325
  73. package/src/util/type.js +0 -6
  74. package/src/util/util.js +0 -66
  75. package/src/version.js +0 -5
  76. package/types/handler/handlerAdmin.d.ts +0 -9
  77. package/types/handler/handlerLogger.d.ts +0 -2
  78. package/types/handler/handlerMetrics.d.ts +0 -25
  79. package/types/handler/handlerUpload.d.ts +0 -7
  80. package/types/handler/plsql/cgi.d.ts +0 -4
  81. package/types/handler/plsql/errorPage.d.ts +0 -11
  82. package/types/handler/plsql/handlerPlSql.d.ts +0 -13
  83. package/types/handler/plsql/parsePage.d.ts +0 -3
  84. package/types/handler/plsql/procedure.d.ts +0 -12
  85. package/types/handler/plsql/procedureError.d.ts +0 -23
  86. package/types/handler/plsql/procedureNamed.d.ts +0 -14
  87. package/types/handler/plsql/procedureSanitize.d.ts +0 -11
  88. package/types/handler/plsql/procedureVariable.d.ts +0 -10
  89. package/types/handler/plsql/request.d.ts +0 -9
  90. package/types/handler/plsql/requestError.d.ts +0 -8
  91. package/types/handler/plsql/sendResponse.d.ts +0 -6
  92. package/types/handler/plsql/upload.d.ts +0 -5
  93. package/types/index.d.ts +0 -11
  94. package/types/server/config.d.ts +0 -2
  95. package/types/server/server.d.ts +0 -74
  96. package/types/types.d.ts +0 -328
  97. package/types/util/cache.d.ts +0 -69
  98. package/types/util/errorToString.d.ts +0 -1
  99. package/types/util/file.d.ts +0 -6
  100. package/types/util/html.d.ts +0 -3
  101. package/types/util/jsonLogger.d.ts +0 -45
  102. package/types/util/oracle.d.ts +0 -5
  103. package/types/util/shutdown.d.ts +0 -2
  104. package/types/util/trace.d.ts +0 -22
  105. package/types/util/type.d.ts +0 -1
  106. package/types/util/util.d.ts +0 -3
  107. package/types/version.d.ts +0 -1
  108. /package/{src/admin/favicon.svg → dist/frontend/assets/favicon-mQAM4tVu.svg} +0 -0
@@ -1,61 +0,0 @@
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">
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">
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">
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">
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">
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">
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
- }
@@ -1,24 +0,0 @@
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
- }
@@ -1,223 +0,0 @@
1
- /**
2
- * Cache statistics for a cache type.
3
- */
4
- export type CacheStats = {
5
- hits: number;
6
- misses: number;
7
- };
8
-
9
- /**
10
- * Cache information for a specific cache type.
11
- */
12
- export type CacheInfo = {
13
- size: number;
14
- stats: CacheStats;
15
- };
16
-
17
- /**
18
- * Cache data for a connection pool.
19
- */
20
- export type CacheData = {
21
- poolName: string;
22
- procedureNameCache: CacheInfo;
23
- argumentCache: CacheInfo;
24
- };
25
-
26
- /**
27
- * Pool statistics.
28
- */
29
- export type PoolStats = {
30
- totalRequests: number;
31
- totalTimeouts: number;
32
- totalRequestsEnqueued: number | undefined;
33
- totalRequestsDequeued: number | undefined;
34
- totalRequestsFailed: number | undefined;
35
- };
36
-
37
- /**
38
- * Pool information.
39
- */
40
- export type PoolInfo = {
41
- name: string;
42
- connectionsInUse: number;
43
- connectionsOpen: number;
44
- stats: PoolStats | null;
45
- };
46
-
47
- /**
48
- * Server metrics.
49
- */
50
- export type Metrics = {
51
- requestCount: number;
52
- errorCount: number;
53
- avgResponseTime: number;
54
- };
55
-
56
- /**
57
- * Server configuration - route.
58
- */
59
- export type RouteConfig = {
60
- route: string;
61
- // PL/SQL specific
62
- user?: string;
63
- password?: string;
64
- connectString?: string;
65
- defaultPage?: string;
66
- pathAlias?: string;
67
- pathAliasProcedure?: string;
68
- documentTable?: string;
69
- exclusionList?: string[];
70
- requestValidationFunction?: string;
71
- transactionMode?: unknown;
72
- errorStyle?: string;
73
- // Static specific
74
- directoryPath?: string;
75
- };
76
-
77
- /**
78
- * Server configuration.
79
- */
80
- export type ServerConfig = {
81
- port: number;
82
- adminRoute?: string;
83
- adminUser?: string;
84
- adminPassword?: string;
85
- loggerFilename: string;
86
- uploadFileSizeLimit?: number;
87
- routePlSql: RouteConfig[];
88
- routeStatic: RouteConfig[];
89
- };
90
-
91
- /**
92
- * Server status response.
93
- */
94
- export type Status = {
95
- version: string;
96
- status: 'running' | 'paused' | 'stopped';
97
- uptime: number;
98
- startTime: string;
99
- metrics: Metrics;
100
- pools: PoolInfo[];
101
- config: Partial<ServerConfig>;
102
- };
103
-
104
- /**
105
- * Historical data for charts.
106
- */
107
- export type HistoryData = {
108
- labels: string[];
109
- requests: number[];
110
- avgResponseTimes: number[];
111
- poolUsage: Record<string, number[]>;
112
- };
113
-
114
- /**
115
- * Chart grid options.
116
- */
117
- export type ChartGridOptions = {
118
- color: string;
119
- };
120
-
121
- /**
122
- * Chart ticks options.
123
- */
124
- export type ChartTicksOptions = {
125
- color: string;
126
- display?: boolean;
127
- };
128
-
129
- /**
130
- * Chart scale options.
131
- */
132
- export type ChartScaleOptions = {
133
- grid?: ChartGridOptions;
134
- ticks?: ChartTicksOptions;
135
- title?: {
136
- display: boolean;
137
- text: string;
138
- color: string;
139
- };
140
- };
141
-
142
- /**
143
- * Chart options.
144
- */
145
- export type ChartOptions = {
146
- scales?: {
147
- x?: ChartScaleOptions;
148
- y?: ChartScaleOptions;
149
- y1?: ChartScaleOptions;
150
- };
151
- plugins?: {
152
- legend?: {
153
- labels?: {
154
- color: string;
155
- };
156
- };
157
- };
158
- };
159
-
160
- /**
161
- * Chart dataset.
162
- */
163
- export type ChartDataset = {
164
- label: string;
165
- data: number[];
166
- borderColor: string;
167
- backgroundColor: string | undefined;
168
- fill: boolean | undefined;
169
- tension: number | undefined;
170
- };
171
-
172
- /**
173
- * Chart data.
174
- */
175
- export type ChartData = {
176
- labels: string[];
177
- datasets: ChartDataset[];
178
- };
179
-
180
- /**
181
- * Chart instance interface.
182
- */
183
- export type ChartInstance = {
184
- data: ChartData;
185
- options: ChartOptions;
186
- update: () => void;
187
- };
188
-
189
- /**
190
- * Error log entry.
191
- */
192
- export type ErrorLog = {
193
- timestamp: string;
194
- req:
195
- | {
196
- method: string;
197
- url: string;
198
- }
199
- | undefined;
200
- message: string;
201
- details:
202
- | {
203
- fullMessage: string;
204
- }
205
- | undefined;
206
- };
207
-
208
- import type {StatusResponse} from './schemas.js';
209
-
210
- /**
211
- * Application state.
212
- */
213
- export type State = {
214
- currentView: string;
215
- status: Partial<StatusResponse>;
216
- maxHistoryPoints: number;
217
- lastRequestCount: number;
218
- lastErrorCount: number;
219
- lastUpdateTime: number;
220
- refreshTimer: ReturnType<typeof setInterval> | null;
221
- history: HistoryData;
222
- charts: Record<string, ChartInstance>;
223
- };
@@ -1,93 +0,0 @@
1
- import type {State, ChartInstance} from '../types.js';
2
-
3
- /**
4
- * Theme colors for charts based on theme mode.
5
- */
6
- interface ChartColors {
7
- gridColor: string;
8
- textColor: string;
9
- }
10
-
11
- /**
12
- * Get chart colors for the current theme.
13
- * @param isDark - Whether dark mode is active.
14
- * @returns Chart color configuration.
15
- */
16
- function getChartColors(isDark: boolean): ChartColors {
17
- return {
18
- gridColor: isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
19
- textColor: isDark ? '#94a3b8' : '#475569',
20
- };
21
- }
22
-
23
- /**
24
- * Update chart options for theme change.
25
- * @param chart - Chart instance to update.
26
- * @param colors - Chart colors for the current theme.
27
- */
28
- function updateChartForTheme(chart: ChartInstance, colors: ChartColors): void {
29
- if (!chart.options.scales) return;
30
-
31
- const scales = chart.options.scales;
32
-
33
- if (scales.x) {
34
- if (scales.x.grid) scales.x.grid.color = colors.gridColor;
35
- if (scales.x.ticks) scales.x.ticks.color = colors.textColor;
36
- }
37
-
38
- if (scales.y?.grid) {
39
- scales.y.grid.color = colors.gridColor;
40
- }
41
- if (scales.y?.ticks) {
42
- scales.y.ticks.color = colors.textColor;
43
- }
44
-
45
- if (scales.y1?.grid) {
46
- scales.y1.grid.color = colors.gridColor;
47
- }
48
- if (scales.y1?.ticks) {
49
- scales.y1.ticks.color = colors.textColor;
50
- }
51
-
52
- if (scales.y?.title) {
53
- scales.y.title.color = colors.textColor;
54
- }
55
-
56
- if (scales.y1?.title) {
57
- scales.y1.title.color = colors.textColor;
58
- }
59
-
60
- if (chart.options.plugins?.legend?.labels) {
61
- chart.options.plugins.legend.labels.color = colors.textColor;
62
- }
63
-
64
- chart.update();
65
- }
66
-
67
- /**
68
- * Initialize theme management.
69
- * @param state - Application state containing chart instances.
70
- */
71
- export function initTheme(state: State): void {
72
- const theme = localStorage.getItem('theme') ?? 'dark';
73
- const btn = document.getElementById('theme-toggle-btn');
74
- if (!btn) return;
75
-
76
- document.body.className = theme;
77
-
78
- btn.onclick = (): void => {
79
- const isDark = document.body.classList.contains('dark');
80
- const newTheme = isDark ? 'light' : 'dark';
81
- document.body.className = newTheme;
82
- localStorage.setItem('theme', newTheme);
83
-
84
- const colors = getChartColors(newTheme === 'dark');
85
-
86
- Object.values(state.charts).forEach((chartInstance): void => {
87
- const chart = chartInstance;
88
- if (chart?.options) {
89
- updateChartForTheme(chart, colors);
90
- }
91
- });
92
- };
93
- }
@@ -1,164 +0,0 @@
1
- import {typedApi} from '../api.js';
2
- import {formatDuration, formatDateTime} from '../util/format.js';
3
- import {errorRow, poolCard} from '../templates/index.js';
4
- import {renderConfig} from '../templates/config.js';
5
- import type {State, ServerConfig} from '../types.js';
6
-
7
- /**
8
- * Refresh the error logs view.
9
- */
10
- export async function refreshErrors(): Promise<void> {
11
- const logs = await typedApi.getErrorLogs();
12
- const tbody = document.querySelector('#errors-table tbody');
13
- if (!tbody) return;
14
-
15
- tbody.innerHTML = logs.map((l) => errorRow(l)).join('');
16
- }
17
-
18
- /**
19
- * Refresh the access logs view.
20
- */
21
- export async function refreshAccess(): Promise<void> {
22
- const result = await typedApi.getAccessLogs();
23
- const el = document.getElementById('access-log-view');
24
- if (!el) return;
25
-
26
- if (Array.isArray(result)) {
27
- el.textContent = result.join('\n');
28
- } else if (result && typeof result === 'object' && 'message' in result) {
29
- el.textContent = result.message;
30
- } else {
31
- el.textContent = 'No logs available';
32
- }
33
-
34
- if (el.parentElement) {
35
- el.parentElement.scrollTop = el.parentElement.scrollHeight;
36
- }
37
- }
38
-
39
- /**
40
- * Refresh the pools view.
41
- *
42
- * @param status - The status data.
43
- */
44
- export function refreshPools(status: Partial<State['status']>): void {
45
- const poolsCont = document.getElementById('pools-container');
46
- if (!poolsCont || !status.pools) return;
47
-
48
- poolsCont.innerHTML = status.pools.map((p) => poolCard(p)).join('');
49
- }
50
-
51
- /**
52
- * Refresh the config view.
53
- *
54
- * @param state - The application state.
55
- */
56
- export function refreshConfig(state: State): void {
57
- const config = state.status.config;
58
- if (!config) return;
59
-
60
- const cont = document.getElementById('config-view');
61
- if (!cont) return;
62
-
63
- // Use type assertion to bypass exactOptionalPropertyTypes check if needed
64
- cont.innerHTML = renderConfig(config as Partial<ServerConfig>);
65
- }
66
-
67
- /**
68
- * Format bytes to human readable format.
69
- *
70
- * @param bytes - The number of bytes.
71
- * @returns Formatted string.
72
- */
73
- function formatBytes(bytes: number): string {
74
- if (bytes === 0) return '0 B';
75
- const k = 1024;
76
- const sizes = ['B', 'KB', 'MB', 'GB'];
77
- const i = Math.floor(Math.log(bytes) / Math.log(k));
78
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
79
- }
80
-
81
- /**
82
- * Format microseconds to milliseconds.
83
- *
84
- * @param microseconds - CPU time in microseconds.
85
- * @returns Formatted string.
86
- */
87
- function formatCpuTime(microseconds: number): string {
88
- const milliseconds = microseconds / 1000;
89
- return `${milliseconds.toFixed(2)} ms`;
90
- }
91
-
92
- /**
93
- * Refresh the system info view.
94
- *
95
- * @param status - The status data.
96
- */
97
- export function refreshSystem(status: Partial<State['status']>): void {
98
- const middlewareVersion = document.getElementById('middleware-version');
99
- if (middlewareVersion && status.version) middlewareVersion.textContent = status.version;
100
-
101
- const nodeVersion = document.getElementById('node-version');
102
- if (nodeVersion && status.system) nodeVersion.textContent = status.system.nodeVersion;
103
-
104
- const platform = document.getElementById('platform-info');
105
- if (platform && status.system) platform.textContent = `${status.system.platform} (${status.system.arch})`;
106
-
107
- const systemUptime = document.getElementById('system-uptime');
108
- if (systemUptime && status.uptime) systemUptime.textContent = formatDuration(status.uptime);
109
-
110
- const systemStartTime = document.getElementById('system-start-time');
111
- if (systemStartTime && status.startTime) systemStartTime.textContent = formatDateTime(status.startTime);
112
-
113
- const systemStatusText = document.getElementById('system-status-text');
114
- if (systemStatusText) systemStatusText.textContent = status.status?.toUpperCase() ?? '-';
115
-
116
- const totalRequests = document.getElementById('total-requests');
117
- if (totalRequests && status.metrics) totalRequests.textContent = status.metrics.requestCount.toLocaleString();
118
-
119
- const totalErrors = document.getElementById('total-errors');
120
- if (totalErrors && status.metrics) totalErrors.textContent = status.metrics.errorCount.toLocaleString();
121
-
122
- const activePools = document.getElementById('active-pools');
123
- if (activePools && status.pools) activePools.textContent = status.pools.length.toString();
124
-
125
- // Memory usage
126
- if (status.system?.memory) {
127
- const memHeapUsed = document.getElementById('memory-heap-used');
128
- if (memHeapUsed) memHeapUsed.textContent = formatBytes(status.system.memory.heapUsed);
129
-
130
- const memHeapTotal = document.getElementById('memory-heap-total');
131
- if (memHeapTotal) memHeapTotal.textContent = formatBytes(status.system.memory.heapTotal);
132
-
133
- const memRss = document.getElementById('memory-rss');
134
- if (memRss) memRss.textContent = formatBytes(status.system.memory.rss);
135
-
136
- const memExternal = document.getElementById('memory-external');
137
- if (memExternal) memExternal.textContent = formatBytes(status.system.memory.external);
138
- }
139
-
140
- // CPU usage
141
- if (status.system?.cpu) {
142
- const cpuUser = document.getElementById('cpu-user');
143
- if (cpuUser) cpuUser.textContent = formatCpuTime(status.system.cpu.user);
144
-
145
- const cpuSystem = document.getElementById('cpu-system');
146
- if (cpuSystem) cpuSystem.textContent = formatCpuTime(status.system.cpu.system);
147
- }
148
-
149
- const systemRefreshStatus = document.getElementById('system-refresh-status');
150
- const autoRefreshToggle = document.getElementById('auto-refresh-toggle') as HTMLInputElement | null;
151
- const refreshInterval = document.getElementById('refresh-interval') as HTMLSelectElement | null;
152
- if (systemRefreshStatus && autoRefreshToggle && refreshInterval) {
153
- if (autoRefreshToggle.checked) {
154
- const intervalSec = parseInt(refreshInterval.value) / 1000;
155
- systemRefreshStatus.textContent = `Active (${intervalSec}s)`;
156
- systemRefreshStatus.classList.add('text-success');
157
- systemRefreshStatus.classList.remove('text-accent');
158
- } else {
159
- systemRefreshStatus.textContent = 'Paused';
160
- systemRefreshStatus.classList.remove('text-success');
161
- systemRefreshStatus.classList.add('text-accent');
162
- }
163
- }
164
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Format a duration in seconds to a human-readable string.
3
- * @param {number} seconds - Duration in seconds.
4
- * @returns {string} Formatted duration string (e.g., "2d 3h 45m 30s").
5
- */
6
- export function formatDuration(seconds: number): string {
7
- const d = Math.floor(seconds / (3600 * 24));
8
- const h = Math.floor((seconds % (3600 * 24)) / 3600);
9
- const m = Math.floor((seconds % 3600) / 60);
10
- const s = Math.floor(seconds % 60);
11
- const parts: string[] = [];
12
- if (d > 0) parts.push(`${d}d`);
13
- if (h > 0) parts.push(`${h}h`);
14
- if (m > 0) parts.push(`${m}m`);
15
- parts.push(`${s}s`);
16
- return parts.join(' ');
17
- }
18
-
19
- /**
20
- * Format an ISO date string to a localized date time string.
21
- * @param isoDate - ISO date string.
22
- * @returns Formatted date time string or "-" if invalid.
23
- */
24
- export function formatDateTime(isoDate: string): string {
25
- if (!isoDate) return '-';
26
- return new Date(isoDate).toLocaleString();
27
- }
@@ -1 +0,0 @@
1
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-bold:700;--tracking-wider:.05em;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-success:#10b981;--color-danger:#ef4444}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.static{position:static}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-3{gap:calc(var(--spacing)*3)}.gap-6{gap:calc(var(--spacing)*6)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.pt-4{padding-top:calc(var(--spacing)*4)}.font-mono{font-family:var(--font-mono)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.text-danger{color:var(--color-danger)}.text-success{color:var(--color-success)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.opacity-75{opacity:.75}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}body{-webkit-font-smoothing:antialiased;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}body.dark{--bg-main:#0f172a;--bg-side:#1e293b;--bg-card:#1e293b;--bg-hover:#334155;--text-main:#94a3b8;--text-bright:#f8fafc;--border:#334155}body:not(.dark){--bg-main:#f8fafc;--bg-side:#fff;--bg-card:#fff;--bg-hover:#f1f5f9;--text-main:#475569;--text-bright:#0f172a;--border:#e2e8f0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}