web_plsql 1.5.1 → 1.7.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 CHANGED
@@ -1,7 +1,8 @@
1
- [![NPM Version][npm-image]][npm-url]
1
+ [![pnpm][pnpm-image]][pnpm-url]
2
+ [![GitHub Release][release-image]][release-url]
2
3
  [![NPM Downloads][downloads-image]][downloads-url]
3
4
  [![Node.js CI](https://github.com/doberkofler/web_plsql/actions/workflows/node.js.yml/badge.svg)](https://github.com/doberkofler/web_plsql/actions/workflows/node.js.yml)
4
- [![Coverage Status](https://coveralls.io/repos/github/doberkofler/web_plsql/badge.svg?branch=master)](https://coveralls.io/github/doberkofler/web_plsql?branch=master)
5
+ [![Coverage Status](https://coveralls.io/repos/github/doberkofler/web_plsql/badge.svg?branch=main)](https://coveralls.io/github/doberkofler/web_plsql?branch=main)
5
6
 
6
7
  # Oracle PL/SQL Gateway Middleware for the Express web framework for Node.js
7
8
  This Express Middleware is a bridge between a PL/SQL application running in an Oracle Database and an Express web server for Node.js.
@@ -14,7 +15,7 @@ Please feel free to try and suggest any improvements. Your thoughts and ideas ar
14
15
  ![Dashboard](docs/images/admin_console.png)
15
16
 
16
17
  # Release History
17
- See the [changelog](https://github.com/doberkofler/web_plsql/blob/master/CHANGELOG.md).
18
+ See the [changelog](https://github.com/doberkofler/web_plsql/blob/main/CHANGELOG.md).
18
19
 
19
20
  # Prerequisites
20
21
  The connection to the Oracle Database uses the node-oracledb Driver for Oracle Database.
@@ -65,13 +66,13 @@ There are 2 options on how to use the web_plsql express middleware:
65
66
  ## Use the predefined `startServer` function
66
67
 
67
68
  The `startServer` API uses a `configType` configuration object. You can review the complete type definitions in the source code:
68
- [src/backend/types.ts](https://github.com/doberkofler/web_plsql/blob/master/src/backend/types.ts)
69
+ [src/backend/types.ts](https://github.com/doberkofler/web_plsql/blob/main/src/backend/types.ts)
69
70
 
70
71
  ## Hand Craft Express Server with Composable Middleware
71
72
 
72
73
  The web_plsql API exports composable middleware components that can be integrated into any Express application.
73
74
  Start by having a look at the build-in server code:
74
- [src/backend/server/server.ts](https://github.com/doberkofler/web_plsql/blob/master/src/backend/server/server.ts)
75
+ [src/backend/server/server.ts](https://github.com/doberkofler/web_plsql/blob/main/src/backend/server/server.ts)
75
76
 
76
77
  ### AdminContext Requirement
77
78
 
@@ -219,7 +220,7 @@ The following mod_plsql DAD configuration translates to the configuration option
219
220
  - The option `auth` allows for custom authentication strategies.
220
221
  - The option `setupExtensions` allows for injecting custom Express routes and middleware.
221
222
  This hook is executed after the database connection pools are initialized but before the static file routes and SPA fallback are mounted.
222
-
223
+
223
224
  ```typescript
224
225
  setupExtensions: async (app, pools) => {
225
226
  // app is the Express application
@@ -229,7 +230,7 @@ The following mod_plsql DAD configuration translates to the configuration option
229
230
  });
230
231
  }
231
232
  ```
232
-
233
+
233
234
  **Basic Authentication**:
234
235
  ```typescript
235
236
  auth: {
@@ -262,8 +263,11 @@ The following mod_plsql DAD configuration translates to the configuration option
262
263
  [MIT](LICENSE)
263
264
 
264
265
 
265
- [npm-image]: https://img.shields.io/npm/v/web_plsql.svg
266
- [npm-url]: https://npmjs.org/package/web_plsql
266
+ [pnpm-image]: https://img.shields.io/badge/pnpm-managed-blueviolet?logo=pnpm
267
+ [pnpm-url]: https://pnpm.io/
268
+
269
+ [release-image]: https://img.shields.io/github/v/release/doberkofler/web_plsql
270
+ [release-url]: https://github.com/doberkofler/web_plsql/releases
267
271
 
268
272
  [downloads-image]: https://img.shields.io/npm/dm/web_plsql.svg
269
273
  [downloads-url]: https://npmjs.org/package/web_plsql
@@ -144,4 +144,4 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
144
144
  <td class="text-right font-mono">${r}</td>
145
145
  </tr>
146
146
  `}).join(``)}catch(e){console.error(`Failed to load stats history:`,e),t&&(t.textContent=`Failed to load stats`)}}async function AC(){let e=document.getElementById(`access-filter`),t=document.getElementById(`access-limit`),n=e?.value??``,r=t?Number.parseInt(t.value):100,i=await Qp.getAccessLogs(r,n),a=document.getElementById(`access-log-view`),o=document.getElementById(`access-log-range`);if(!a)return;let s=0;Array.isArray(i)?(a.textContent=i.join(`
147
- `),s=i.length):i&&typeof i==`object`&&`message`in i?(a.textContent=i.message,s=0):(a.textContent=`No logs available`,s=0),o&&(o.textContent=s>0?`Showing last ${s} log entries`:`No logs available`),a.parentElement&&(a.parentElement.scrollTop=a.parentElement.scrollHeight)}async function jC(){let e=document.getElementById(`trace-filter`),t=document.getElementById(`trace-limit`),n=document.getElementById(`trace-status-toggle`),r=e?.value??``,i=t?Number.parseInt(t.value):100;try{let{enabled:e}=await Qp.getTraceStatus();n&&(n.textContent=e?`Tracing: ON`:`Tracing: OFF`,n.dataset.enabled=String(e),n.className=`px-4 py-2 rounded-lg text-sm font-bold transition-colors ${e?`bg-success text-black hover:bg-success/80`:`bg-white/10 text-white hover:bg-white/20`}`)}catch(e){console.error(`Failed to get trace status`,e)}let a=await Qp.getTraceLogs(i,r);new DC(`trace-table`,{columns:[{id:`timestamp`,header:`Timestamp`,headerTitle:`Time of request`,width:`1%`,accessor:e=>new Date(e.timestamp).toLocaleString(),className:`font-mono text-xs text-dim whitespace-nowrap`},{id:`source`,header:`Source`,headerTitle:`Request source (IP/User)`,width:`1%`,accessor:e=>e.source,className:`text-dim text-xs truncate max-w-[150px]`,cellTitle:e=>e.source},{id:`method`,header:`Method`,headerTitle:`HTTP Method`,width:`1%`,accessor:e=>e.method,className:`font-mono text-xs whitespace-nowrap`},{id:`url`,header:`Request URL`,headerTitle:`Request URL`,accessor:e=>e.url,className:`text-bright font-mono text-xs truncate max-w-[800px]`,cellTitle:e=>e.url},{id:`status`,header:`Status`,headerTitle:`HTTP Status Code`,width:`1%`,accessor:e=>e.status,className:e=>`font-bold text-xs uppercase whitespace-nowrap ${e.status===`success`?`text-success`:`text-danger`}`},{id:`duration`,header:`Duration`,headerTitle:`Execution time in ms`,width:`1%`,align:`right`,accessor:e=>gC(e.duration),className:`font-mono text-xs text-dim whitespace-nowrap`}],dense:!0,tableLayout:`auto`,onRowClick:e=>{let t=document.getElementById(`trace-modal`),n=document.getElementById(`trace-detail-content`);t&&n&&(n.textContent=JSON.stringify(e,null,2),t.style.display=`flex`)}}).render(a)}function MC(e){let t=document.getElementById(`pools-container`);!t||!e.pools||(t.innerHTML=e.pools.map(e=>SC(e)).join(``))}function NC(e){let t=e.status.config;if(!t)return;let n=document.getElementById(`config-view`);n&&(n.innerHTML=EC(t))}function PC(e){if(e===0)return`0 B`;let t=1024,n=[`B`,`KB`,`MB`,`GB`],r=Math.floor(Math.log(e)/Math.log(t));return`${Number.parseFloat((e/t**+r).toFixed(2))} ${n[r]}`}function FC(e){return gC(e/1e3)}function IC(e,t){let n=document.getElementById(`middleware-version`);n&&e.version&&(n.textContent=`${e.version} (3/29/2026, 2:11:03 PM)`,n.title=`The version of the web_plsql gateway and admin console build time`);let r=document.getElementById(`node-version`);r&&e.system&&(r.textContent=e.system.nodeVersion,r.title=`The version of the Node.js runtime`);let i=document.getElementById(`platform-info`);i&&e.system&&(i.textContent=`${e.system.platform} (${e.system.arch})`,i.title=`The operating system and architecture`);let a=document.getElementById(`system-uptime`);a&&e.uptime&&(a.textContent=hC(e.uptime));let o=document.getElementById(`system-start-time`);o&&e.startTime&&(o.textContent=_C(e.startTime));let s=document.getElementById(`system-status-text`);s&&(s.textContent=e.status?.toUpperCase()??`-`);let c=document.getElementById(`total-requests`);c&&e.metrics&&(c.textContent=e.metrics.requestCount.toLocaleString(),c.title=`Cumulative number of requests handled by the server`);let l=document.getElementById(`total-errors`);l&&e.metrics&&(l.textContent=e.metrics.errorCount.toLocaleString(),l.title=`Cumulative number of failed requests`);let u=document.getElementById(`active-pools`);if(u&&e.pools&&(u.textContent=e.pools.length.toString(),u.title=`Number of database connection pools currently active`),e.system){let t=e.system,n={heapUsed:t.memory.heapUsed,heapTotal:t.memory.heapTotal,rss:t.memory.rss,external:t.memory.external,cpuUser:t.cpu.user,cpuSystem:t.cpu.system},r=(e,r,i)=>{let a=n[r],o=t.memory[i],s=document.getElementById(e),c=document.getElementById(`${e}-max`);s&&(s.textContent=PC(a)),c&&typeof o==`number`&&(c.textContent=PC(o))},i=(e,r,i)=>{let a=n[r],o=document.getElementById(e);if(o&&(o.textContent=FC(a)),i){let n=t.cpu[i],r=document.getElementById(`${e}-max`);r&&typeof n==`number`&&(r.textContent=FC(n))}};r(`memory-heap-used`,`heapUsed`,`heapUsedMax`),r(`memory-heap-total`,`heapTotal`,`heapTotalMax`),r(`memory-rss`,`rss`,`rssMax`),r(`memory-external`,`external`,`externalMax`),i(`cpu-user`,`cpuUser`,`userMax`),i(`cpu-system`,`cpuSystem`,`systemMax`);let a=document.getElementById(`cpu-percent`);a&&t.cpu.max!==void 0&&(a.textContent=`${t.cpu.max.toFixed(1)}%`,a.title=`Max System CPU usage`);let o=document.getElementById(`memory-percent`);o&&t.memory.totalMemory&&(o.textContent=`${(t.memory.rss/t.memory.totalMemory*100).toFixed(1)}%`,o.title=`System Memory Used relative to Total (${PC(t.memory.totalMemory)})`);let s=document.getElementById(`cpu-cores-info`);s&&t.cpuCores&&(s.textContent=`${t.cpuCores} Cores`)}}async function LC(e,t){let n=e.querySelector(`.material-symbols-rounded`),r=n?.textContent;e.disabled=!0,e.classList.add(`loading`),n&&(n.textContent=`sync`,n.classList.add(`animate-spin`));try{return await t()}finally{e.disabled=!1,e.classList.remove(`loading`),n&&(n.textContent=r??``,n.classList.remove(`animate-spin`))}}function RC(e,t){let n=document.getElementById(e);return n&&n.addEventListener(`click`,()=>void LC(n,t)),n}var zC={overview:`dashboard`,trace:`bolt`,errors:`error`,access:`list_alt`,pools:`database`,stats:`query_stats`,config:`settings`,system:`terminal`},$={currentView:`overview`,status:{},maxHistoryPoints:30,lastRequestCount:0,lastErrorCount:0,lastUpdateTime:Date.now(),lastBucketTimestamp:0,nextRefreshTimeout:null,nextRefreshTime:0,countdownInterval:null,history:{labels:[],requests:[],avgResponseTimes:[],p95ResponseTimes:[],p99ResponseTimes:[],cpuUsage:[],memoryUsage:[],poolUsage:{}},charts:{},metricsMin:{},metricsMax:{}},BC={REFRESH_INTERVAL:`admin_refresh_interval`,HISTORY_DURATION:`admin_history_duration`,AUTO_REFRESH:`admin_auto_refresh`,THEME:`theme`,LAST_VIEW:`admin_last_view`};function VC(e){console.error(`Connection lost:`,e);let t=document.getElementById(`server-status-dot`),n=document.getElementById(`server-status-text`);t&&(t.className=`dot offline`),n&&(n.textContent=`OFFLINE`);let r=document.getElementById(`btn-pause`);r&&(r.style.display=`none`);let i=document.getElementById(`btn-resume`);i&&(i.style.display=`none`);let a=document.querySelector(`#system-status .dot`),o=document.getElementById(`system-status-text`);a&&(a.className=`dot offline`),o&&(o.textContent=`OFFLINE`);let s=document.getElementById(`offline-modal`);s&&s.style.display!==`flex`&&(s.style.display=`flex`),GC(6e4),HC(6e4)}function HC(e){$.countdownInterval&&clearInterval($.countdownInterval);let t=Date.now()+e,n=document.getElementById(`offline-countdown`),r=document.getElementById(`reconnect-text`);r&&(r.textContent=`Try Reconnect`);let i=()=>{if(!n)return;let e=Math.max(0,Math.ceil((t-Date.now())/1e3));n.textContent=`Retrying in ${e}s...`,e<=0&&$.countdownInterval&&(clearInterval($.countdownInterval),$.countdownInterval=null)};i(),$.countdownInterval=setInterval(i,1e3)}function UC(){$.countdownInterval&&=(clearInterval($.countdownInterval),null);let e=document.getElementById(`offline-countdown`);e&&(e.textContent=``)}async function WC(e=!1,t=!1){let n=document.getElementById(`reconnect-spinner`),r=document.getElementById(`reconnect-icon`),i=document.getElementById(`reconnect-text`);n&&(n.style.display=`inline-block`),r&&(r.style.display=`none`),i&&(i.textContent=`Connecting...`);try{let a=Date.now(),o=await Qp.getStatus(e,t),s=document.getElementById(`offline-modal`);if(s?.style.display===`flex`&&s&&(s.style.display=`none`,UC()),n&&(n.style.display=`none`),r&&(r.style.display=`inline-block`),i&&(i.textContent=`Try Reconnect`),o.history&&$.history.labels.length===0&&pC($,o.history),!o.config&&$.status.config&&(o.config=$.status.config),$.status=o,!o.metrics||!o.pools)return;let c=o.history?.at(-1),l=(o.intervalMs??5e3)/1e3,u=c?c.requests/l:0,d=c?c.durationAvg:o.metrics.avgResponseTime;if($.lastRequestCount=o.metrics.requestCount,$.lastErrorCount=o.metrics.errorCount,$.lastUpdateTime=a,o.system){let e=o.system;vC({heapUsed:e.memory.heapUsed,heapTotal:e.memory.heapTotal,rss:e.memory.rss,external:e.memory.external,cpuUser:e.cpu.user,cpuSystem:e.cpu.system},$.metricsMin,$.metricsMax)}c&&c.timestamp>$.lastBucketTimestamp?($.lastBucketTimestamp=c.timestamp,mC($,new Date(c.timestamp).toLocaleTimeString(),u,d,o.pools)):!c&&$.history.labels.length===0&&mC($,new Date().toLocaleTimeString(),0,0,o.pools);let f=document.getElementById(`sidebar-version`);f&&(f.textContent=`v${o.version}`);let p=document.getElementById(`uptime-val`);p&&(p.textContent=hC(o.uptime));let m=document.getElementById(`start-time-val`);m&&(m.textContent=`Started: ${_C(o.startTime)}`);let h=document.getElementById(`req-per-sec`);h&&(h.textContent=u.toFixed(2));let g=document.getElementById(`max-req-per-sec`);g&&(g.textContent=o.metrics.maxRequestsPerSecond.toFixed(2));let _=document.getElementById(`avg-resp-time`);_&&(_.textContent=`${o.metrics.avgResponseTime.toFixed(1)}ms`);let v=document.getElementById(`max-resp-time`);v&&(v.textContent=`${o.metrics.maxResponseTime.toFixed(1)}ms`);let y=document.getElementById(`err-count`);y&&(y.textContent=o.metrics.errorCount.toLocaleString());let b=0,x=0;o.pools.forEach(e=>{e.cache&&(b+=e.cache.procedureName.hits+e.cache.argument.hits,x+=e.cache.procedureName.misses+e.cache.argument.misses)});let S=b+x,C=S>0?Math.round(b/S*100):0,w=document.getElementById(`cache-hit-rate-val`);w&&(w.textContent=`${C}%`,w.style.color=C>80?`var(--success)`:C>50?`var(--warning)`:`var(--danger)`);let T=document.getElementById(`cache-hits-val`);T&&(T.textContent=b.toLocaleString());let E=document.getElementById(`cache-misses-val`);E&&(E.textContent=x.toLocaleString());let ee=document.getElementById(`server-status-dot`),te=document.getElementById(`server-status-text`);ee&&(ee.className=`dot `+o.status),te&&(te.textContent=o.status.toUpperCase());let ne=document.getElementById(`btn-pause`);ne&&(ne.style.display=o.status===`running`?`flex`:`none`);let re=document.getElementById(`btn-resume`);re&&(re.style.display=o.status===`paused`?`flex`:`none`),$.currentView===`errors`&&await OC(),$.currentView===`trace`&&await jC(),$.currentView===`pools`&&MC(o),$.currentView===`config`&&NC($),$.currentView===`system`&&IC(o,$),YC?.checked&&XC&&GC(Number.parseInt(XC.value,10))}catch(e){document.getElementById(`offline-modal`)?.style.display===`flex`?(GC(6e4),HC(6e4),n&&(n.style.display=`none`),r&&(r.style.display=`inline-block`),i&&(i.textContent=`Try Reconnect`)):VC(e)}}function GC(e){$.nextRefreshTimeout&&=(clearTimeout($.nextRefreshTimeout),null),$.nextRefreshTime=Date.now()+e,$.nextRefreshTimeout=setTimeout(()=>{WC()},e)}function KC(){let e=document.getElementById(`refresh-interval`);e&&GC(Number.parseInt(e.value))}function qC(){$.nextRefreshTimeout&&=(clearTimeout($.nextRefreshTimeout),null)}function JC(){let e=document.getElementById(`chart-history-points`);e&&Array.from(e.options).forEach(e=>{let t=Number.parseInt(e.value),n=``;if(t<60)n=`${t}s`;else if(t<3600)n=`${Math.round(t/60)} min`;else{let e=t/3600;n=e===Math.round(e)?`${e} hour${e>1?`s`:``}`:`${e.toFixed(1)} hours`}e.textContent=`Last ${n}`})}document.querySelectorAll(`nav button`).forEach(e=>{let t=e;t.addEventListener(`click`,async()=>{try{let e=t.dataset.view;if(!e)return;$.currentView=e,localStorage.setItem(BC.LAST_VIEW,e),document.querySelectorAll(`.view`).forEach(e=>{let t=e;t.style.display=`none`});let n=document.getElementById(`view-`+e);n&&(n.style.display=`block`),document.querySelectorAll(`nav button`).forEach(e=>e.classList.remove(`active`)),t.classList.add(`active`);let r=document.getElementById(`view-title`);if(r){let n=Array.from(t.childNodes).filter(e=>e.nodeType===Node.TEXT_NODE).map(e=>e.textContent?.trim()).join(``);r.innerHTML=`<span class="material-symbols-rounded">${zC[e]??`chevron_right`}</span>${n}`}e===`errors`&&await OC(),e===`trace`&&await jC(),e===`pools`&&MC($.status),e===`stats`&&await kC(),e===`config`&&NC($),e===`system`&&IC($.status,$)}catch(e){VC(e)}})});var YC=document.getElementById(`auto-refresh-toggle`);YC&&YC.addEventListener(`change`,e=>{let t=e.target;localStorage.setItem(BC.AUTO_REFRESH,String(t.checked)),t.checked?KC():qC()});var XC=document.getElementById(`refresh-interval`);XC&&XC.addEventListener(`change`,()=>{localStorage.setItem(BC.REFRESH_INTERVAL,XC.value);let e=Number.parseInt(XC.value),t=Number.parseInt(ZC?.value??`60`);$.maxHistoryPoints=Math.max(1,Math.floor(t/(e/1e3))),$.history.labels=[],$.history.requests=[],$.history.avgResponseTimes=[],$.history.cpuUsage=[],$.history.memoryUsage=[],$.history.poolUsage={},JC(),IC($.status,$),WC()});var ZC=document.getElementById(`chart-history-points`);ZC&&ZC.addEventListener(`change`,()=>{localStorage.setItem(BC.HISTORY_DURATION,ZC.value);let e=document.getElementById(`refresh-interval`);if(!e)return;let t=Number.parseInt(e.value),n=Number.parseInt(ZC.value);$.maxHistoryPoints=Math.max(1,Math.floor(n/(t/1e3))),$.history.labels=[],$.history.requests=[],$.history.avgResponseTimes=[],$.history.cpuUsage=[],$.history.memoryUsage=[],$.history.poolUsage={},WC()});var QC=document.getElementById(`manual-refresh`);QC&&QC.addEventListener(`click`,()=>{LC(QC,WC)});var $C=document.getElementById(`error-filter`);$C&&$C.addEventListener(`keydown`,e=>{e.key===`Enter`&&OC()}),RC(`error-load-btn`,OC),RC(`trace-load-btn`,jC),RC(`access-load-btn`,AC),RC(`stats-load-btn`,kC);var ew=document.getElementById(`trace-filter`);ew&&ew.addEventListener(`keydown`,e=>{e.key===`Enter`&&jC()});var tw=document.getElementById(`trace-clear-btn`);tw&&tw.addEventListener(`click`,async()=>{confirm(`Are you sure you want to clear all traces?`)&&await LC(tw,async()=>{await Qp.clearTraces(),await jC()})});var nw=document.getElementById(`trace-status-toggle`);nw&&nw.addEventListener(`click`,async()=>{await LC(nw,async()=>{let{enabled:e}=await Qp.getTraceStatus();await Qp.toggleTrace(!e),await jC()})});var rw=document.getElementById(`access-filter`);rw&&rw.addEventListener(`keydown`,e=>{e.key===`Enter`&&AC()});var iw=document.getElementById(`stats-limit`);iw&&iw.addEventListener(`keydown`,e=>{e.key===`Enter`&&kC()}),RC(`btn-pause`,async()=>{confirm(`Are you sure you want to pause the server?`)&&(await Qp.post(`api/server/pause`),await WC())}),RC(`btn-resume`,async()=>{await Qp.post(`api/server/resume`),await WC()}),RC(`btn-stop`,async()=>{confirm(`Are you sure you want to STOP the server? This action cannot be undone from the web interface.`)&&(await Qp.post(`api/server/stop`),alert(`Server stop requested. The interface will now become unresponsive.`))}),RC(`btn-clear-all-cache`,async()=>{confirm(`Are you sure you want to clear ALL caches?`)&&(await Qp.post(`api/cache/clear`),await WC())});var aw=document.getElementById(`btn-reconnect`);aw&&aw.addEventListener(`click`,()=>{LC(aw,WC)}),globalThis.addEventListener(`keydown`,e=>{if(e.key===`Escape`){let e=document.getElementById(`error-modal`);e&&(e.style.display=`none`);let t=document.getElementById(`trace-modal`);t&&(t.style.display=`none`)}});var ow=localStorage.getItem(BC.REFRESH_INTERVAL);ow&&XC&&(XC.value=ow);var sw=localStorage.getItem(BC.HISTORY_DURATION);sw&&ZC&&(ZC.value=sw);var cw=localStorage.getItem(BC.AUTO_REFRESH);if(cw!==null&&YC&&(YC.checked=cw===`true`),xC($),fC($),JC(),XC&&ZC){let e=Number.parseInt(XC.value),t=Number.parseInt(ZC.value);$.maxHistoryPoints=Math.max(1,Math.floor(t/(e/1e3)))}var lw=localStorage.getItem(BC.LAST_VIEW);if(lw){let e=document.querySelector(`nav button[data-view="${lw}"]`);e&&e.click()}WC(!0,!0).then(()=>{YC?.checked&&KC()}).catch(console.error);
147
+ `),s=i.length):i&&typeof i==`object`&&`message`in i?(a.textContent=i.message,s=0):(a.textContent=`No logs available`,s=0),o&&(o.textContent=s>0?`Showing last ${s} log entries`:`No logs available`),a.parentElement&&(a.parentElement.scrollTop=a.parentElement.scrollHeight)}async function jC(){let e=document.getElementById(`trace-filter`),t=document.getElementById(`trace-limit`),n=document.getElementById(`trace-status-toggle`),r=e?.value??``,i=t?Number.parseInt(t.value):100;try{let{enabled:e}=await Qp.getTraceStatus();n&&(n.textContent=e?`Tracing: ON`:`Tracing: OFF`,n.dataset.enabled=String(e),n.className=`px-4 py-2 rounded-lg text-sm font-bold transition-colors ${e?`bg-success text-black hover:bg-success/80`:`bg-white/10 text-white hover:bg-white/20`}`)}catch(e){console.error(`Failed to get trace status`,e)}let a=await Qp.getTraceLogs(i,r);new DC(`trace-table`,{columns:[{id:`timestamp`,header:`Timestamp`,headerTitle:`Time of request`,width:`1%`,accessor:e=>new Date(e.timestamp).toLocaleString(),className:`font-mono text-xs text-dim whitespace-nowrap`},{id:`source`,header:`Source`,headerTitle:`Request source (IP/User)`,width:`1%`,accessor:e=>e.source,className:`text-dim text-xs truncate max-w-[150px]`,cellTitle:e=>e.source},{id:`method`,header:`Method`,headerTitle:`HTTP Method`,width:`1%`,accessor:e=>e.method,className:`font-mono text-xs whitespace-nowrap`},{id:`url`,header:`Request URL`,headerTitle:`Request URL`,accessor:e=>e.url,className:`text-bright font-mono text-xs truncate max-w-[800px]`,cellTitle:e=>e.url},{id:`status`,header:`Status`,headerTitle:`HTTP Status Code`,width:`1%`,accessor:e=>e.status,className:e=>`font-bold text-xs uppercase whitespace-nowrap ${e.status===`success`?`text-success`:`text-danger`}`},{id:`duration`,header:`Duration`,headerTitle:`Execution time in ms`,width:`1%`,align:`right`,accessor:e=>gC(e.duration),className:`font-mono text-xs text-dim whitespace-nowrap`}],dense:!0,tableLayout:`auto`,onRowClick:e=>{let t=document.getElementById(`trace-modal`),n=document.getElementById(`trace-detail-content`);t&&n&&(n.textContent=JSON.stringify(e,null,2),t.style.display=`flex`)}}).render(a)}function MC(e){let t=document.getElementById(`pools-container`);!t||!e.pools||(t.innerHTML=e.pools.map(e=>SC(e)).join(``))}function NC(e){let t=e.status.config;if(!t)return;let n=document.getElementById(`config-view`);n&&(n.innerHTML=EC(t))}function PC(e){if(e===0)return`0 B`;let t=1024,n=[`B`,`KB`,`MB`,`GB`],r=Math.floor(Math.log(e)/Math.log(t));return`${Number.parseFloat((e/t**+r).toFixed(2))} ${n[r]}`}function FC(e){return gC(e/1e3)}function IC(e,t){let n=document.getElementById(`middleware-version`);n&&e.version&&(n.textContent=`${e.version} (4/8/2026, 11:51:20 AM)`,n.title=`The version of the web_plsql gateway and admin console build time`);let r=document.getElementById(`node-version`);r&&e.system&&(r.textContent=e.system.nodeVersion,r.title=`The version of the Node.js runtime`);let i=document.getElementById(`platform-info`);i&&e.system&&(i.textContent=`${e.system.platform} (${e.system.arch})`,i.title=`The operating system and architecture`);let a=document.getElementById(`system-uptime`);a&&e.uptime&&(a.textContent=hC(e.uptime));let o=document.getElementById(`system-start-time`);o&&e.startTime&&(o.textContent=_C(e.startTime));let s=document.getElementById(`system-status-text`);s&&(s.textContent=e.status?.toUpperCase()??`-`);let c=document.getElementById(`total-requests`);c&&e.metrics&&(c.textContent=e.metrics.requestCount.toLocaleString(),c.title=`Cumulative number of requests handled by the server`);let l=document.getElementById(`total-errors`);l&&e.metrics&&(l.textContent=e.metrics.errorCount.toLocaleString(),l.title=`Cumulative number of failed requests`);let u=document.getElementById(`active-pools`);if(u&&e.pools&&(u.textContent=e.pools.length.toString(),u.title=`Number of database connection pools currently active`),e.system){let t=e.system,n={heapUsed:t.memory.heapUsed,heapTotal:t.memory.heapTotal,rss:t.memory.rss,external:t.memory.external,cpuUser:t.cpu.user,cpuSystem:t.cpu.system},r=(e,r,i)=>{let a=n[r],o=t.memory[i],s=document.getElementById(e),c=document.getElementById(`${e}-max`);s&&(s.textContent=PC(a)),c&&typeof o==`number`&&(c.textContent=PC(o))},i=(e,r,i)=>{let a=n[r],o=document.getElementById(e);if(o&&(o.textContent=FC(a)),i){let n=t.cpu[i],r=document.getElementById(`${e}-max`);r&&typeof n==`number`&&(r.textContent=FC(n))}};r(`memory-heap-used`,`heapUsed`,`heapUsedMax`),r(`memory-heap-total`,`heapTotal`,`heapTotalMax`),r(`memory-rss`,`rss`,`rssMax`),r(`memory-external`,`external`,`externalMax`),i(`cpu-user`,`cpuUser`,`userMax`),i(`cpu-system`,`cpuSystem`,`systemMax`);let a=document.getElementById(`cpu-percent`);a&&t.cpu.max!==void 0&&(a.textContent=`${t.cpu.max.toFixed(1)}%`,a.title=`Max System CPU usage`);let o=document.getElementById(`memory-percent`);o&&t.memory.totalMemory&&(o.textContent=`${(t.memory.rss/t.memory.totalMemory*100).toFixed(1)}%`,o.title=`System Memory Used relative to Total (${PC(t.memory.totalMemory)})`);let s=document.getElementById(`cpu-cores-info`);s&&t.cpuCores&&(s.textContent=`${t.cpuCores} Cores`)}}async function LC(e,t){let n=e.querySelector(`.material-symbols-rounded`),r=n?.textContent;e.disabled=!0,e.classList.add(`loading`),n&&(n.textContent=`sync`,n.classList.add(`animate-spin`));try{return await t()}finally{e.disabled=!1,e.classList.remove(`loading`),n&&(n.textContent=r??``,n.classList.remove(`animate-spin`))}}function RC(e,t){let n=document.getElementById(e);return n&&n.addEventListener(`click`,()=>void LC(n,t)),n}var zC={overview:`dashboard`,trace:`bolt`,errors:`error`,access:`list_alt`,pools:`database`,stats:`query_stats`,config:`settings`,system:`terminal`},$={currentView:`overview`,status:{},maxHistoryPoints:30,lastRequestCount:0,lastErrorCount:0,lastUpdateTime:Date.now(),lastBucketTimestamp:0,nextRefreshTimeout:null,nextRefreshTime:0,countdownInterval:null,history:{labels:[],requests:[],avgResponseTimes:[],p95ResponseTimes:[],p99ResponseTimes:[],cpuUsage:[],memoryUsage:[],poolUsage:{}},charts:{},metricsMin:{},metricsMax:{}},BC={REFRESH_INTERVAL:`admin_refresh_interval`,HISTORY_DURATION:`admin_history_duration`,AUTO_REFRESH:`admin_auto_refresh`,THEME:`theme`,LAST_VIEW:`admin_last_view`};function VC(e){console.error(`Connection lost:`,e);let t=document.getElementById(`server-status-dot`),n=document.getElementById(`server-status-text`);t&&(t.className=`dot offline`),n&&(n.textContent=`OFFLINE`);let r=document.getElementById(`btn-pause`);r&&(r.style.display=`none`);let i=document.getElementById(`btn-resume`);i&&(i.style.display=`none`);let a=document.querySelector(`#system-status .dot`),o=document.getElementById(`system-status-text`);a&&(a.className=`dot offline`),o&&(o.textContent=`OFFLINE`);let s=document.getElementById(`offline-modal`);s&&s.style.display!==`flex`&&(s.style.display=`flex`),GC(6e4),HC(6e4)}function HC(e){$.countdownInterval&&clearInterval($.countdownInterval);let t=Date.now()+e,n=document.getElementById(`offline-countdown`),r=document.getElementById(`reconnect-text`);r&&(r.textContent=`Try Reconnect`);let i=()=>{if(!n)return;let e=Math.max(0,Math.ceil((t-Date.now())/1e3));n.textContent=`Retrying in ${e}s...`,e<=0&&$.countdownInterval&&(clearInterval($.countdownInterval),$.countdownInterval=null)};i(),$.countdownInterval=setInterval(i,1e3)}function UC(){$.countdownInterval&&=(clearInterval($.countdownInterval),null);let e=document.getElementById(`offline-countdown`);e&&(e.textContent=``)}async function WC(e=!1,t=!1){let n=document.getElementById(`reconnect-spinner`),r=document.getElementById(`reconnect-icon`),i=document.getElementById(`reconnect-text`);n&&(n.style.display=`inline-block`),r&&(r.style.display=`none`),i&&(i.textContent=`Connecting...`);try{let a=Date.now(),o=await Qp.getStatus(e,t),s=document.getElementById(`offline-modal`);if(s?.style.display===`flex`&&s&&(s.style.display=`none`,UC()),n&&(n.style.display=`none`),r&&(r.style.display=`inline-block`),i&&(i.textContent=`Try Reconnect`),o.history&&$.history.labels.length===0&&pC($,o.history),!o.config&&$.status.config&&(o.config=$.status.config),$.status=o,!o.metrics||!o.pools)return;let c=o.history?.at(-1),l=(o.intervalMs??5e3)/1e3,u=c?c.requests/l:0,d=c?c.durationAvg:o.metrics.avgResponseTime;if($.lastRequestCount=o.metrics.requestCount,$.lastErrorCount=o.metrics.errorCount,$.lastUpdateTime=a,o.system){let e=o.system;vC({heapUsed:e.memory.heapUsed,heapTotal:e.memory.heapTotal,rss:e.memory.rss,external:e.memory.external,cpuUser:e.cpu.user,cpuSystem:e.cpu.system},$.metricsMin,$.metricsMax)}c&&c.timestamp>$.lastBucketTimestamp?($.lastBucketTimestamp=c.timestamp,mC($,new Date(c.timestamp).toLocaleTimeString(),u,d,o.pools)):!c&&$.history.labels.length===0&&mC($,new Date().toLocaleTimeString(),0,0,o.pools);let f=document.getElementById(`sidebar-version`);f&&(f.textContent=`v${o.version}`);let p=document.getElementById(`uptime-val`);p&&(p.textContent=hC(o.uptime));let m=document.getElementById(`start-time-val`);m&&(m.textContent=`Started: ${_C(o.startTime)}`);let h=document.getElementById(`req-per-sec`);h&&(h.textContent=u.toFixed(2));let g=document.getElementById(`max-req-per-sec`);g&&(g.textContent=o.metrics.maxRequestsPerSecond.toFixed(2));let _=document.getElementById(`avg-resp-time`);_&&(_.textContent=`${o.metrics.avgResponseTime.toFixed(1)}ms`);let v=document.getElementById(`max-resp-time`);v&&(v.textContent=`${o.metrics.maxResponseTime.toFixed(1)}ms`);let y=document.getElementById(`err-count`);y&&(y.textContent=o.metrics.errorCount.toLocaleString());let b=0,x=0;o.pools.forEach(e=>{e.cache&&(b+=e.cache.procedureName.hits+e.cache.argument.hits,x+=e.cache.procedureName.misses+e.cache.argument.misses)});let S=b+x,C=S>0?Math.round(b/S*100):0,w=document.getElementById(`cache-hit-rate-val`);w&&(w.textContent=`${C}%`,w.style.color=C>80?`var(--success)`:C>50?`var(--warning)`:`var(--danger)`);let T=document.getElementById(`cache-hits-val`);T&&(T.textContent=b.toLocaleString());let E=document.getElementById(`cache-misses-val`);E&&(E.textContent=x.toLocaleString());let ee=document.getElementById(`server-status-dot`),te=document.getElementById(`server-status-text`);ee&&(ee.className=`dot `+o.status),te&&(te.textContent=o.status.toUpperCase());let ne=document.getElementById(`btn-pause`);ne&&(ne.style.display=o.status===`running`?`flex`:`none`);let re=document.getElementById(`btn-resume`);re&&(re.style.display=o.status===`paused`?`flex`:`none`),$.currentView===`errors`&&await OC(),$.currentView===`trace`&&await jC(),$.currentView===`pools`&&MC(o),$.currentView===`config`&&NC($),$.currentView===`system`&&IC(o,$),YC?.checked&&XC&&GC(Number.parseInt(XC.value,10))}catch(e){document.getElementById(`offline-modal`)?.style.display===`flex`?(GC(6e4),HC(6e4),n&&(n.style.display=`none`),r&&(r.style.display=`inline-block`),i&&(i.textContent=`Try Reconnect`)):VC(e)}}function GC(e){$.nextRefreshTimeout&&=(clearTimeout($.nextRefreshTimeout),null),$.nextRefreshTime=Date.now()+e,$.nextRefreshTimeout=setTimeout(()=>{WC()},e)}function KC(){let e=document.getElementById(`refresh-interval`);e&&GC(Number.parseInt(e.value))}function qC(){$.nextRefreshTimeout&&=(clearTimeout($.nextRefreshTimeout),null)}function JC(){let e=document.getElementById(`chart-history-points`);e&&Array.from(e.options).forEach(e=>{let t=Number.parseInt(e.value),n=``;if(t<60)n=`${t}s`;else if(t<3600)n=`${Math.round(t/60)} min`;else{let e=t/3600;n=e===Math.round(e)?`${e} hour${e>1?`s`:``}`:`${e.toFixed(1)} hours`}e.textContent=`Last ${n}`})}document.querySelectorAll(`nav button`).forEach(e=>{let t=e;t.addEventListener(`click`,async()=>{try{let e=t.dataset.view;if(!e)return;$.currentView=e,localStorage.setItem(BC.LAST_VIEW,e),document.querySelectorAll(`.view`).forEach(e=>{let t=e;t.style.display=`none`});let n=document.getElementById(`view-`+e);n&&(n.style.display=`block`),document.querySelectorAll(`nav button`).forEach(e=>e.classList.remove(`active`)),t.classList.add(`active`);let r=document.getElementById(`view-title`);if(r){let n=Array.from(t.childNodes).filter(e=>e.nodeType===Node.TEXT_NODE).map(e=>e.textContent?.trim()).join(``);r.innerHTML=`<span class="material-symbols-rounded">${zC[e]??`chevron_right`}</span>${n}`}e===`errors`&&await OC(),e===`trace`&&await jC(),e===`pools`&&MC($.status),e===`stats`&&await kC(),e===`config`&&NC($),e===`system`&&IC($.status,$)}catch(e){VC(e)}})});var YC=document.getElementById(`auto-refresh-toggle`);YC&&YC.addEventListener(`change`,e=>{let t=e.target;localStorage.setItem(BC.AUTO_REFRESH,String(t.checked)),t.checked?KC():qC()});var XC=document.getElementById(`refresh-interval`);XC&&XC.addEventListener(`change`,()=>{localStorage.setItem(BC.REFRESH_INTERVAL,XC.value);let e=Number.parseInt(XC.value),t=Number.parseInt(ZC?.value??`60`);$.maxHistoryPoints=Math.max(1,Math.floor(t/(e/1e3))),$.history.labels=[],$.history.requests=[],$.history.avgResponseTimes=[],$.history.cpuUsage=[],$.history.memoryUsage=[],$.history.poolUsage={},JC(),IC($.status,$),WC()});var ZC=document.getElementById(`chart-history-points`);ZC&&ZC.addEventListener(`change`,()=>{localStorage.setItem(BC.HISTORY_DURATION,ZC.value);let e=document.getElementById(`refresh-interval`);if(!e)return;let t=Number.parseInt(e.value),n=Number.parseInt(ZC.value);$.maxHistoryPoints=Math.max(1,Math.floor(n/(t/1e3))),$.history.labels=[],$.history.requests=[],$.history.avgResponseTimes=[],$.history.cpuUsage=[],$.history.memoryUsage=[],$.history.poolUsage={},WC()});var QC=document.getElementById(`manual-refresh`);QC&&QC.addEventListener(`click`,()=>{LC(QC,WC)});var $C=document.getElementById(`error-filter`);$C&&$C.addEventListener(`keydown`,e=>{e.key===`Enter`&&OC()}),RC(`error-load-btn`,OC),RC(`trace-load-btn`,jC),RC(`access-load-btn`,AC),RC(`stats-load-btn`,kC);var ew=document.getElementById(`trace-filter`);ew&&ew.addEventListener(`keydown`,e=>{e.key===`Enter`&&jC()});var tw=document.getElementById(`trace-clear-btn`);tw&&tw.addEventListener(`click`,async()=>{confirm(`Are you sure you want to clear all traces?`)&&await LC(tw,async()=>{await Qp.clearTraces(),await jC()})});var nw=document.getElementById(`trace-status-toggle`);nw&&nw.addEventListener(`click`,async()=>{await LC(nw,async()=>{let{enabled:e}=await Qp.getTraceStatus();await Qp.toggleTrace(!e),await jC()})});var rw=document.getElementById(`access-filter`);rw&&rw.addEventListener(`keydown`,e=>{e.key===`Enter`&&AC()});var iw=document.getElementById(`stats-limit`);iw&&iw.addEventListener(`keydown`,e=>{e.key===`Enter`&&kC()}),RC(`btn-pause`,async()=>{confirm(`Are you sure you want to pause the server?`)&&(await Qp.post(`api/server/pause`),await WC())}),RC(`btn-resume`,async()=>{await Qp.post(`api/server/resume`),await WC()}),RC(`btn-stop`,async()=>{confirm(`Are you sure you want to STOP the server? This action cannot be undone from the web interface.`)&&(await Qp.post(`api/server/stop`),alert(`Server stop requested. The interface will now become unresponsive.`))}),RC(`btn-clear-all-cache`,async()=>{confirm(`Are you sure you want to clear ALL caches?`)&&(await Qp.post(`api/cache/clear`),await WC())});var aw=document.getElementById(`btn-reconnect`);aw&&aw.addEventListener(`click`,()=>{LC(aw,WC)}),globalThis.addEventListener(`keydown`,e=>{if(e.key===`Escape`){let e=document.getElementById(`error-modal`);e&&(e.style.display=`none`);let t=document.getElementById(`trace-modal`);t&&(t.style.display=`none`)}});var ow=localStorage.getItem(BC.REFRESH_INTERVAL);ow&&XC&&(XC.value=ow);var sw=localStorage.getItem(BC.HISTORY_DURATION);sw&&ZC&&(ZC.value=sw);var cw=localStorage.getItem(BC.AUTO_REFRESH);if(cw!==null&&YC&&(YC.checked=cw===`true`),xC($),fC($),JC(),XC&&ZC){let e=Number.parseInt(XC.value),t=Number.parseInt(ZC.value);$.maxHistoryPoints=Math.max(1,Math.floor(t/(e/1e3)))}var lw=localStorage.getItem(BC.LAST_VIEW);if(lw){let e=document.querySelector(`nav button[data-view="${lw}"]`);e&&e.click()}WC(!0,!0).then(()=>{YC?.checked&&KC()}).catch(console.error);
@@ -6,7 +6,7 @@
6
6
  <title>web_plsql Admin Console</title>
7
7
  <link rel="icon" type="image/svg+xml" href="/admin/assets/favicon-mQAM4tVu.svg" />
8
8
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
9
- <script type="module" crossorigin src="/admin/assets/main-C8XFvlzY.js"></script>
9
+ <script type="module" crossorigin src="/admin/assets/main-DurwHchn.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/admin/assets/main-CoERzZAr.css">
11
11
  </head>
12
12
  <body class="dark">
@@ -513,4 +513,4 @@
513
513
  </div>
514
514
  </div>
515
515
  </body>
516
- </html>
516
+ </html>
Binary file
Binary file
package/dist/index.d.mts CHANGED
@@ -293,7 +293,7 @@ declare global {
293
293
  }
294
294
  /**
295
295
  * Returns the current library version
296
- * @returns Version.
296
+ * @returns {string} - Version.
297
297
  */
298
298
  declare const getVersion: () => string;
299
299
  //#endregion
package/dist/index.mjs CHANGED
@@ -207,9 +207,9 @@ const DB_TYPE_DATE = oracledb.DB_TYPE_DATE;
207
207
  globalThis.__VERSION__ ??= "**development**";
208
208
  /**
209
209
  * Returns the current library version
210
- * @returns Version.
210
+ * @returns {string} - Version.
211
211
  */
212
- const getVersion = () => "1.5.1";
212
+ const getVersion = () => "1.7.0";
213
213
  //#endregion
214
214
  //#region src/backend/server/config.ts
215
215
  const paddedLine = (title, value) => {
@@ -224,30 +224,31 @@ const showConfig = (config) => {
224
224
  console.log(LINE);
225
225
  console.log(`NODE PL/SQL SERVER version ${getVersion()}`);
226
226
  console.log(LINE);
227
- paddedLine("Started", (/* @__PURE__ */ new Date()).toString());
228
227
  paddedLine("Server port", config.port);
229
228
  paddedLine("Admin route", `${config.adminRoute ?? "/admin"}${config.adminUser ? " (authenticated)" : ""}`);
230
229
  paddedLine("Access log", config.loggerFilename.length > 0 ? config.loggerFilename : "-");
231
230
  paddedLine("Upload file size limit", typeof config.uploadFileSizeLimit === "number" ? `${config.uploadFileSizeLimit} bytes` : "-");
232
- if (config.routeStatic.length > 0) config.routeStatic.forEach((e) => {
233
- paddedLine("Static route", e.route);
234
- paddedLine("Directory path", e.directoryPath);
231
+ if (config.routeStatic.length > 0) config.routeStatic.forEach((e, i) => {
232
+ console.log(`Static route #${i + 1}:`);
233
+ paddedLine(" Route", e.route);
234
+ paddedLine(" Path", e.directoryPath);
235
235
  });
236
- if (config.routePlSql.length > 0) config.routePlSql.forEach((e) => {
236
+ if (config.routePlSql.length > 0) config.routePlSql.forEach((e, i) => {
237
+ console.log(`PL/SQL route #${i + 1}:`);
237
238
  let transactionMode = "";
238
239
  if (typeof e.transactionMode === "string") transactionMode = e.transactionMode;
239
240
  else if (typeof e.transactionMode === "function") transactionMode = "custom callback";
240
- paddedLine("Route", `http://localhost:${config.port}${e.route}`);
241
- paddedLine("Oracle user", e.user);
242
- paddedLine("Oracle server", e.connectString);
243
- paddedLine("Oracle document table", e.documentTable);
244
- paddedLine("Default page", e.defaultPage.length > 0 ? e.defaultPage : "-");
245
- paddedLine("Path alias", e.pathAlias ?? "-");
246
- paddedLine("Path alias procedure", e.pathAliasProcedure ?? "-");
247
- paddedLine("Exclution list", e.exclusionList ? e.exclusionList.join(", ") : "-");
248
- paddedLine("Validation function", e.requestValidationFunction ?? "-");
249
- paddedLine("After request handler", transactionMode.length > 0 ? transactionMode : "-");
250
- paddedLine("Error style", e.errorStyle);
241
+ paddedLine(" Route", `http://localhost:${config.port}${e.route}`);
242
+ paddedLine(" Oracle user", e.user);
243
+ paddedLine(" Oracle server", e.connectString);
244
+ paddedLine(" Oracle document table", e.documentTable);
245
+ paddedLine(" Default page", e.defaultPage.length > 0 ? e.defaultPage : "-");
246
+ paddedLine(" Path alias", e.pathAlias ?? "-");
247
+ paddedLine(" Path alias procedure", e.pathAliasProcedure ?? "-");
248
+ paddedLine(" Exclution list", e.exclusionList ? e.exclusionList.join(", ") : "-");
249
+ paddedLine(" Validation function", e.requestValidationFunction ?? "-");
250
+ paddedLine(" After request handler", transactionMode.length > 0 ? transactionMode : "-");
251
+ paddedLine(" Error style", e.errorStyle);
251
252
  });
252
253
  console.log(LINE);
253
254
  const baseUrl = `http://localhost:${config.port}`;