Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aaf3c9cb1c | |||
| abde872ab2 | |||
| ca2d2b09ad | |||
| fb7d4d988b | |||
| 26e6eea5d5 | |||
| 2458dd08d8 | |||
| dee648b3bc | |||
| f4ed32cee4 | |||
| e9c72952ab | |||
| 1bd485c43e | |||
| 421a0390ba | |||
| c7f87a7c22 | |||
| 390d5c648f | |||
| ec651c1cdb | |||
| 6f82c393e7 | |||
| afdb48367b | |||
| 53526ca3ba | |||
| 07e8f4489b | |||
| 14101a09d3 | |||
| 5344d53806 | |||
| 971535926c | |||
| c13a4ae4be | |||
| e7a03c48ae | |||
| a682329a3f | |||
| c4580f9874 | |||
| b331065b8c | |||
| 4675ca3e89 | |||
| 70e2c8e17d | |||
| db53d87cc5 | |||
| ff6244d3d1 | |||
| f0aafe9027 | |||
| 487f2acac8 | |||
| 0a5e35c58e | |||
| 34c0cab5dc | |||
| 3a666e9300 | |||
| cbe1b5d37d | |||
| 30f2044d9f | |||
| 593b000ca3 | |||
| 60c298c396 | |||
| d7f1c16454 | |||
| 4290d4be86 | |||
| bc34cb5eab | |||
| eda12f3ce3 | |||
| 65f19aac72 | |||
| 29a992a695 | |||
| dbb2166a8f | |||
| 22691329a5 | |||
| e098e1a2ad | |||
| 16d64ec988 | |||
| cb1332ff76 | |||
| 3e52060788 | |||
| f041891a3f | |||
| f902c2c1db | |||
| e1a9e1f997 | |||
| d7b39a3017 | |||
| 0f41b0d8c7 | |||
| 2d33c037ba | |||
| dca7b37eb8 | |||
| b56598ba00 | |||
| bbf550b183 | |||
| f4fc5eb1fd | |||
| d9e88cf5f9 | |||
| eccb9706f2 | |||
| 285e681413 | |||
| 4f3958d94d | |||
| d19f22255d | |||
| 87ec55619a | |||
| b91dab0f85 | |||
| df573d498e | |||
| da2b838019 | |||
| 107adeee1d | |||
| 45f933b473 | |||
| ad16bc44f1 | |||
| 96d5b7e01a | |||
| 93ffcf86b3 | |||
| de98b070db | |||
| d3d2bde440 | |||
| 0840b2b571 | |||
| fa2e784eaa | |||
| 64f2854023 | |||
| 03e3261755 | |||
| c724e68b8c | |||
| f8f66d1392 | |||
| c66bdc9f88 | |||
| 8d57547ace | |||
| 54eaf23298 | |||
| 7148306381 | |||
| d3aefef78d | |||
| ecd0cc0066 | |||
| eac490297a | |||
| de65641f6f | |||
| ffddc1a5f5 | |||
| 26152e0520 | |||
| f79ad07a57 | |||
| 76d5b9bf7c | |||
| 670b67eecf | |||
| 174af5cf86 | |||
| a1f5e45e94 | |||
| d06165bd0c | |||
| 8f3c6fdf23 | |||
| 106ef2919e | |||
| 3d7fd233cf | |||
| 34d40f7370 | |||
| 89b9d01628 | |||
| ed3964e892 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,3 +21,4 @@ dist_*/
|
|||||||
**/.claude/settings.local.json
|
**/.claude/settings.local.json
|
||||||
.nogit/data/
|
.nogit/data/
|
||||||
readme.plan.md
|
readme.plan.md
|
||||||
|
.playwright-mcp/
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
[ 74ms] TypeError: Cannot read properties of null (reading 'appendChild')
|
|
||||||
at TypedserverStatusPill.show (http://localhost:3000/typedserver/devtools:17607:21)
|
|
||||||
at TypedserverStatusPill.updateStatus (http://localhost:3000/typedserver/devtools:17567:10)
|
|
||||||
at ReloadChecker.checkReload (http://localhost:3000/typedserver/devtools:18137:23)
|
|
||||||
at async ReloadChecker.start (http://localhost:3000/typedserver/devtools:18224:9)
|
|
||||||
[ 587ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 697ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[ 669ms] [WARNING] Lit is in dev mode. Not recommended for production! See https://lit.dev/msg/dev-mode for more information. @ http://localhost:3000/chunk-3L5NJTXF.js:13541
|
|
||||||
[ 729ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:3000/favicon.ico:0
|
|
||||||
[ 27973ms] [ERROR] WebSocket connection to 'ws://localhost:3000/ws/reload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/main.js:115
|
|
||||||
[ 27973ms] [ERROR] [ReloadService] WebSocket error: Event @ http://localhost:3000/main.js:141
|
|
||||||
[ 29975ms] [ERROR] WebSocket connection to 'ws://localhost:3000/ws/reload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/main.js:115
|
|
||||||
[ 29975ms] [ERROR] [ReloadService] WebSocket error: Event @ http://localhost:3000/main.js:141
|
|
||||||
[ 33977ms] [ERROR] WebSocket connection to 'ws://localhost:3000/ws/reload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/main.js:115
|
|
||||||
[ 33978ms] [ERROR] [ReloadService] WebSocket error: Event @ http://localhost:3000/main.js:141
|
|
||||||
[ 41980ms] [ERROR] WebSocket connection to 'ws://localhost:3000/ws/reload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/main.js:115
|
|
||||||
[ 41980ms] [ERROR] [ReloadService] WebSocket error: Event @ http://localhost:3000/main.js:141
|
|
||||||
[ 51983ms] [ERROR] WebSocket connection to 'ws://localhost:3000/ws/reload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/main.js:115
|
|
||||||
[ 51983ms] [ERROR] [ReloadService] WebSocket error: Event @ http://localhost:3000/main.js:141
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[ 55ms] TypeError: Cannot read properties of null (reading 'appendChild')
|
|
||||||
at TypedserverStatusPill.show (http://localhost:3000/typedserver/devtools:17607:21)
|
|
||||||
at TypedserverStatusPill.updateStatus (http://localhost:3000/typedserver/devtools:17567:10)
|
|
||||||
at ReloadChecker.checkReload (http://localhost:3000/typedserver/devtools:18137:23)
|
|
||||||
at async ReloadChecker.start (http://localhost:3000/typedserver/devtools:18224:9)
|
|
||||||
[ 791ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
[ 272ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for Refresh-cw
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078)
|
|
||||||
at async N._$EP (http://localhost:3000/bundle.js:1:9024) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 272ms] [WARNING] Lucide icon 'Refresh-cw' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 274ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for Pause-circle
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078)
|
|
||||||
at async N._$EP (http://localhost:3000/bundle.js:1:9024) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 274ms] [WARNING] Lucide icon 'Pause-circle' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 275ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for Refresh-cw
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 275ms] [WARNING] Lucide icon 'Refresh-cw' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 276ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for Refresh-cw
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078)
|
|
||||||
at async N._$EP (http://localhost:3000/bundle.js:1:9024) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 276ms] [WARNING] Lucide icon 'Refresh-cw' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 276ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for Refresh-cw
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 276ms] [WARNING] Lucide icon 'Refresh-cw' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 297ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 377ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
|
|
||||||
[ 78064ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 78237ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
|
|
||||||
[ 127969ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
|
|
||||||
[ 127969ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
|
|
||||||
[ 129695ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
|
|
||||||
[ 129695ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
|
|
||||||
[ 133309ms] [ERROR] WebSocket connection to 'ws://localhost:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/typedserver/devtools:16227
|
|
||||||
[ 133309ms] [ERROR] TypedSocket WebSocket error: Event @ http://localhost:3000/typedserver/devtools:16251
|
|
||||||
[ 141762ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 141910ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[ 437ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
[ 38948ms] [WARNING] FontAwesome icon not found: circle-check @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 52895ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 52896ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 52896ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 52897ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 99401ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 99401ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
[ 75ms] TypeError: Cannot read properties of null (reading 'appendChild')
|
|
||||||
at TypedserverStatusPill.show (http://localhost:3000/typedserver/devtools:17607:21)
|
|
||||||
at TypedserverStatusPill.updateStatus (http://localhost:3000/typedserver/devtools:17567:10)
|
|
||||||
at ReloadChecker.checkReload (http://localhost:3000/typedserver/devtools:18137:23)
|
|
||||||
at async ReloadChecker.start (http://localhost:3000/typedserver/devtools:18224:9)
|
|
||||||
[ 763ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
[ 22315ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 22315ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 22316ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 22316ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 22321ms] [ERROR] method: >>listApiTokens<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 22322ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 22322ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 22322ms] [ERROR] method: >>listApiTokens<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 65371ms] [ERROR] method: >>createApiToken<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 65371ms] [ERROR] Failed to create token: zs @ http://localhost:3000/bundle.js:38142
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
[ 642ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
[ 114916ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
[ 179731ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 179731ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 179731ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 179732ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 179737ms] [ERROR] method: >>listApiTokens<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 179738ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 179738ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 179738ms] [ERROR] method: >>listApiTokens<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[ 603ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[ 308ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 309ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 309ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 310ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 349ms] [ERROR] method: >>listApiTokens<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 350ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 350ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 351ms] [ERROR] method: >>listApiTokens<< got an ERROR: "admin access required" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 500ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/apitokens:0
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
[ 427ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
[ 44124ms] [WARNING] FontAwesome icon not found: circle-check @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 59106ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 59106ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 59107ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 59107ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 59116ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 59116ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
[ 89192ms] [ERROR] Error rendering Lucide icon: Error: Could not create element for MagnifyingGlass
|
|
||||||
at N.updated (http://localhost:3000/bundle.js:1204:736)
|
|
||||||
at N._$AE (http://localhost:3000/bundle.js:1:9837)
|
|
||||||
at N.performUpdate (http://localhost:3000/bundle.js:1:9701)
|
|
||||||
at N.scheduleUpdate (http://localhost:3000/bundle.js:1:9170)
|
|
||||||
at N._$EP (http://localhost:3000/bundle.js:1:9078) @ http://localhost:3000/bundle.js:1203
|
|
||||||
[ 89192ms] [WARNING] Lucide icon 'MagnifyingGlass' not found in lucideIcons object @ http://localhost:3000/bundle.js:1174
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[ 95ms] TypeError: Cannot read properties of null (reading 'appendChild')
|
|
||||||
at TypedserverStatusPill.show (http://localhost:3000/typedserver/devtools:17607:21)
|
|
||||||
at TypedserverStatusPill.updateStatus (http://localhost:3000/typedserver/devtools:17567:10)
|
|
||||||
at ReloadChecker.checkReload (http://localhost:3000/typedserver/devtools:18137:23)
|
|
||||||
at async ReloadChecker.start (http://localhost:3000/typedserver/devtools:18224:9)
|
|
||||||
[ 992ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/overview:0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[ 329ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 727ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
|
|
||||||
[ 260513ms] [ERROR] method: >>adminLoginWithUsernameAndPassword<< got an ERROR: "login failed" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 260514ms] [ERROR] Login failed: Ns @ http://localhost:3000/bundle.js:38066
|
|
||||||
[ 260518ms] [WARNING] FontAwesome icon not found: circle-xmark @ http://localhost:3000/bundle.js:1203
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[ 397ms] [ERROR] method: >>getMergedRoutes<< got an ERROR: "unauthorized" with data undefined @ http://localhost:3000/bundle.js:13
|
|
||||||
[ 657ms] [ERROR] Error while trying to use the following icon from the Manifest: http://localhost:3000/assetbroker/manifest/icon-144x144.png (Download error or resource isn't a valid image) @ http://localhost:3000/routes:0
|
|
||||||
[ 24180ms] [WARNING] FontAwesome icon not found: circle-check @ http://localhost:3000/bundle.js:1203
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
318
changelog.md
318
changelog.md
@@ -1,5 +1,323 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-06 - 11.1.0 - feat(apiclient)
|
||||||
|
add TypeScript API client (ts_apiclient) with resource managers and package exports
|
||||||
|
|
||||||
|
- Add new ts_apiclient module providing DcRouterApiClient and resource managers: routes, certificates, api tokens, remote ingress, emails, stats, config, logs, and radius (with sub-managers).
|
||||||
|
- Add resource classes and builders (Route, RemoteIngress, ApiToken, Certificate, Email) and convenience manager APIs for common operations.
|
||||||
|
- Export apiclient in package.json (exports and files) and add ts_apiclient index and plugins wrapper for @api.global/typedrequest.
|
||||||
|
- Add comprehensive tests for the API client (test/test.apiclient.ts).
|
||||||
|
- Bump devDependencies: @git.zone/tsbuild -> ^4.3.0 and @types/node -> ^25.3.5
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.51 - fix(build)
|
||||||
|
include HTML files in tsbundle output and bump tsbuild/tsbundle devDependencies
|
||||||
|
|
||||||
|
- Add includeFiles: ["./html/**/*.html"] to bundler config in npmextra.json so HTML assets are included in the bundle
|
||||||
|
- Bump devDependencies: @git.zone/tsbuild ^4.2.4 -> ^4.2.6, @git.zone/tsbundle ^2.9.0 -> ^2.9.1 (non-breaking tooling updates)
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.50 - fix(devDependencies)
|
||||||
|
bump @git.zone/tsbuild to ^4.2.4
|
||||||
|
|
||||||
|
- updated devDependency @git.zone/tsbuild from ^4.2.3 to ^4.2.4
|
||||||
|
- no other package changes
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.49 - fix(dcrouter)
|
||||||
|
no changes detected
|
||||||
|
|
||||||
|
- No files changed in this commit
|
||||||
|
- Working tree unchanged; no version bump required
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.48 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild to ^4.2.3
|
||||||
|
|
||||||
|
- package.json: updated devDependency @git.zone/tsbuild from ^4.2.2 to ^4.2.3
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.47 - fix(dcrouter)
|
||||||
|
no code changes; nothing to release
|
||||||
|
|
||||||
|
- No files changed in this commit (git diff is empty)
|
||||||
|
- No version bump required
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.46 - fix(none)
|
||||||
|
no changes detected
|
||||||
|
|
||||||
|
- Git diff reported no changes
|
||||||
|
- No files were modified; no version bump required
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.45 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild to ^4.2.2
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild from ^4.2.1 to ^4.2.2 in package.json
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.44 - fix(dev-deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.2.1
|
||||||
|
|
||||||
|
- Updated package.json devDependency @git.zone/tsbuild from ^4.2.0 to ^4.2.1
|
||||||
|
- Non-breaking patch update for build tool dependency
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.43 - fix(dcrouter)
|
||||||
|
no changes detected; nothing to release
|
||||||
|
|
||||||
|
- Git diff reported no changes
|
||||||
|
- No files were modified, so no version bump is recommended
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.42 - fix(dcrouter)
|
||||||
|
empty commit — no changes
|
||||||
|
|
||||||
|
- No files were modified in this commit
|
||||||
|
- No version bump required
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.41 - fix(deps)
|
||||||
|
bump devDependency @git.zone/tsbuild to ^4.2.0
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild from ^4.1.26 to ^4.2.0
|
||||||
|
- Change made in package.json under devDependencies
|
||||||
|
- No source code changes — dev tooling dependency bump
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.40 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.26
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild: ^4.1.25 → ^4.1.26 in package.json
|
||||||
|
- Build tooling/dev dependency bump only; no runtime or API changes
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.39 - fix(devDependencies)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.25
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.24 to ^4.1.25 in package.json
|
||||||
|
- Only a devDependency was changed; no runtime dependencies or source files modified
|
||||||
|
- Current package version is 11.0.38; recommend a patch release
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.38 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild to ^4.1.24
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild in devDependencies from ^4.1.23 to ^4.1.24
|
||||||
|
- Dev tooling dependency bump; no runtime or API changes expected
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.37 - fix(dcrouter)
|
||||||
|
bump patch version (no changes detected)
|
||||||
|
|
||||||
|
- No files changed in the provided diff
|
||||||
|
- Current package version is 11.0.36 (package.json)
|
||||||
|
- Recommend a patch bump to record a new release if desired
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.36 - fix(repo)
|
||||||
|
no changes detected; no release necessary
|
||||||
|
|
||||||
|
- Diff contains no changes
|
||||||
|
- No files were modified — skip version bump
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.35 - fix(dev-deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.23
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.22 to ^4.1.23 in package.json
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.34 - fix(dcrouter)
|
||||||
|
empty diff — no changes detected; no version bump suggested
|
||||||
|
|
||||||
|
- No file changes in the provided git diff
|
||||||
|
- Current package.json version is 11.0.33 — keep unchanged
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.33 - fix(build)
|
||||||
|
bump @git.zone/tsbuild to ^4.1.22
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.21 to ^4.1.22
|
||||||
|
- Change affects build tooling only (devDependencies) — no runtime or API changes expected
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.32 - fix(dev-deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.21
|
||||||
|
|
||||||
|
- Updated package.json devDependency @git.zone/tsbuild from ^4.1.20 to ^4.1.21
|
||||||
|
- Change affects development tooling only (no runtime/source changes)
|
||||||
|
- Bump package patch version from 11.0.31 to 11.0.32 recommended
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.31 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.20
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.19 to ^4.1.20
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.30 - fix(devDependencies)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.19
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild from ^4.1.18 to ^4.1.19 in package.json
|
||||||
|
- Change is limited to devDependencies (build toolchain) and should not affect runtime behavior
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.29 - fix(build)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.18
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild from ^4.1.17 to ^4.1.18
|
||||||
|
- Change is a devDependency update only; no runtime behavior expected to change
|
||||||
|
- Recommend patch version bump
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.28 - fix(devDependencies)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.17
|
||||||
|
|
||||||
|
- package.json: updated @git.zone/tsbuild from ^4.1.16 to ^4.1.17 (devDependency)
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.27 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild to ^4.1.16
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.15 to ^4.1.16 in package.json
|
||||||
|
- No runtime code or dependency changes; only a dev/build tool bump
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.26 - fix(devDependencies)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.15
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.14 to ^4.1.15 in package.json
|
||||||
|
- No runtime changes; development tooling update only
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.25 - fix(logger)
|
||||||
|
remove build verification comment from logger export
|
||||||
|
|
||||||
|
- Removed parenthetical '(build verification)' from export comment in ts/logger.ts
|
||||||
|
- No functional changes — this is a comment-only cleanup
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.24 - fix(dcrouter)
|
||||||
|
no changes detected — no release necessary
|
||||||
|
|
||||||
|
- No files changed in the provided diff; no code, docs, or dependency updates to release.
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.23 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.14
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.13 to ^4.1.14 in package.json
|
||||||
|
- Change affects build tooling only (devDependencies); no production code changes
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.22 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.13
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild from ^4.1.9 to ^4.1.13 in devDependencies
|
||||||
|
- No runtime code changes; build/dev dependency update only
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.21 - fix()
|
||||||
|
no changes detected
|
||||||
|
|
||||||
|
- No files changed in this diff; no release required.
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.20 - fix(logger)
|
||||||
|
annotate singleton logger export comment for build verification
|
||||||
|
|
||||||
|
- Changed comment in ts/logger.ts to add '(build verification)'
|
||||||
|
- No functional code changes; only a comment update
|
||||||
|
- Intended to mark the export for build verification purposes
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.19 - fix(dcrouter)
|
||||||
|
no changes
|
||||||
|
|
||||||
|
- No files changed in this commit.
|
||||||
|
- Package version remains 11.0.18.
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.18 - fix(dcrouter)
|
||||||
|
no changes detected; no version bump required
|
||||||
|
|
||||||
|
- Git diff contains no changes — nothing to release
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.17 - fix(dcrouter)
|
||||||
|
no changes detected in diff; no code or documentation updates
|
||||||
|
|
||||||
|
- No files changed in this diff
|
||||||
|
- No code, tests, or documentation modified; no release required
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.16 - fix(dcrouter)
|
||||||
|
noop commit: no changes detected
|
||||||
|
|
||||||
|
- No files changed in this diff.
|
||||||
|
- No code or configuration modifications detected.
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.15 - fix()
|
||||||
|
no changes detected; no version bump necessary
|
||||||
|
|
||||||
|
- Diff contains no changes; no files were modified
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.14 - fix(dcrouter)
|
||||||
|
no changes detected
|
||||||
|
|
||||||
|
- Provided git diff contains no changes; nothing to release or bump
|
||||||
|
- Create a commit only if an empty/placeholder commit is intentionally required
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.13 - fix()
|
||||||
|
no code changes
|
||||||
|
|
||||||
|
- No files were changed in this commit.
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.12 - fix(dcrouter)
|
||||||
|
no changes detected — nothing to commit
|
||||||
|
|
||||||
|
- Diff reported: No changes
|
||||||
|
- No files were modified or staged; no functional or documentation changes to release
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.11 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.9
|
||||||
|
|
||||||
|
- Updated @git.zone/tsbuild from ^4.1.4 to ^4.1.9 in package.json
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.10 - fix(playwright-mcp)
|
||||||
|
remove committed Playwright artifacts and add .playwright-mcp/ to .gitignore
|
||||||
|
|
||||||
|
- Added .playwright-mcp/ to .gitignore to avoid committing transient Playwright outputs
|
||||||
|
- Removed many Playwright-generated logs, screenshots and console dumps under .playwright-mcp/ to reduce repository noise/size
|
||||||
|
- Prevents accidental check-in of large test artifacts generated by Playwright runs
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.9 - fix(devDependencies)
|
||||||
|
bump @git.zone/tsbuild devDependency to ^4.1.4
|
||||||
|
|
||||||
|
- package.json: Updated @git.zone/tsbuild from ^4.1.3 to ^4.1.4
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.8 - fix()
|
||||||
|
no changes detected
|
||||||
|
|
||||||
|
- No files changed in this commit
|
||||||
|
- No version bump recommended
|
||||||
|
|
||||||
|
## 2026-03-05 - 11.0.7 - fix(deps)
|
||||||
|
bump @git.zone/tsbuild to ^4.1.3 and @push.rocks/lik to ^6.3.1
|
||||||
|
|
||||||
|
- Updated devDependency @git.zone/tsbuild from ^4.1.2 to ^4.1.3 in package.json
|
||||||
|
- Updated dependency @push.rocks/lik from ^6.2.2 to ^6.3.1 in package.json
|
||||||
|
- Changes are non-breaking dependency bumps; no source code changes
|
||||||
|
|
||||||
|
## 2026-03-04 - 11.0.5 - fix(none)
|
||||||
|
no changes detected; nothing to release
|
||||||
|
|
||||||
|
- Diff contained no changes
|
||||||
|
- No files modified; no version bump required
|
||||||
|
|
||||||
|
## 2026-03-04 - 11.0.4 - fix()
|
||||||
|
no changes
|
||||||
|
|
||||||
|
- No files changed in the provided diff; no release or version bump required.
|
||||||
|
|
||||||
|
## 2026-03-04 - 11.0.3 - fix()
|
||||||
|
no changes detected
|
||||||
|
|
||||||
|
- Diff shows no file changes; no code changes to release.
|
||||||
|
|
||||||
|
## 2026-03-04 - 11.0.2 - fix(dcrouter)
|
||||||
|
no changes detected; no files were modified
|
||||||
|
|
||||||
|
- diff was empty
|
||||||
|
- no source or package changes detected
|
||||||
|
|
||||||
|
## 2026-03-04 - 11.0.1 - fix(auth)
|
||||||
|
treat expired JWTs as no identity, improve logout and token verification flow, and bump deps
|
||||||
|
|
||||||
|
- App: getActionContext now treats expired JWTs as null to avoid using stale identities for requests.
|
||||||
|
- Logout action always clears local login state; server-side adminLogout is attempted only when a valid identity exists.
|
||||||
|
- Dashboard: verify persisted JWT with server (verifyIdentity) on startup; if verification fails, clear state and show login.
|
||||||
|
- Auto-refresh: on combined refresh failure, detect auth-related errors (invalid/unauthorized/401), dispatch logout and reload to force re-login.
|
||||||
|
- Deps: bumped devDependencies @git.zone/tstest (^3.2.0) and @git.zone/tswatch (^3.2.5); added runtime dependency @push.rocks/lik (^6.2.2).
|
||||||
|
- Tests/artifacts: added Playwright console logs and page screenshots (test artifacts) to the commit.
|
||||||
|
|
||||||
|
## 2026-03-03 - 11.0.0 - BREAKING CHANGE(opsserver)
|
||||||
|
Require authentication for OpsServer endpoints, split handlers into authenticated view/admin routers, and make identity required on many TypedRequest interfaces
|
||||||
|
|
||||||
|
- Added viewRouter and adminRouter to OpsServer and wired middleware to enforce identity/admin checks (requireValidIdentity, requireAdminIdentity).
|
||||||
|
- Moved handlers to appropriate routers (viewRouter for read endpoints, adminRouter for write/admin endpoints) instead of registering on the unauthenticated main typedrouter.
|
||||||
|
- Made identity a required field on numerous ts_interfaces request types (breaking change to request typings).
|
||||||
|
- Refactored ApiTokenHandler to register directly on adminRouter and use dataArg.identity.userId (no per-handler admin checks needed thanks to middleware).
|
||||||
|
- Updated tests: added admin login to obtain identity, adjusted protected endpoint tests to expect rejection when unauthenticated, and adapted other tests to pass identity where required.
|
||||||
|
- Added IReq_GetNetworkStats request/response typings to ts_interfaces/requests/stats.ts.
|
||||||
|
- Bumped dependencies: @api.global/typedrequest ^3.3.0 and @api.global/typedserver ^8.4.2.
|
||||||
|
|
||||||
## 2026-03-03 - 10.1.9 - fix(deps)
|
## 2026-03-03 - 10.1.9 - fix(deps)
|
||||||
bump @push.rocks/smartproxy to ^25.9.1
|
bump @push.rocks/smartproxy to ^25.9.1
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
"to": "./dist_serve/bundle.js",
|
"to": "./dist_serve/bundle.js",
|
||||||
"outputMode": "bundle",
|
"outputMode": "bundle",
|
||||||
"bundler": "esbuild",
|
"bundler": "esbuild",
|
||||||
"production": true
|
"production": true,
|
||||||
|
"includeFiles": ["./html/**/*.html"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "10.1.9",
|
"version": "11.1.0",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist_ts/index.js",
|
".": "./dist_ts/index.js",
|
||||||
"./interfaces": "./dist_ts_interfaces/index.js"
|
"./interfaces": "./dist_ts_interfaces/index.js",
|
||||||
|
"./apiclient": "./dist_ts_apiclient/index.js"
|
||||||
},
|
},
|
||||||
"author": "Task Venture Capital GmbH",
|
"author": "Task Venture Capital GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -19,21 +20,22 @@
|
|||||||
"watch": "tswatch"
|
"watch": "tswatch"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^4.1.2",
|
"@git.zone/tsbuild": "^4.3.0",
|
||||||
"@git.zone/tsbundle": "^2.9.0",
|
"@git.zone/tsbundle": "^2.9.1",
|
||||||
"@git.zone/tsrun": "^2.0.1",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tstest": "^3.1.8",
|
"@git.zone/tstest": "^3.2.0",
|
||||||
"@git.zone/tswatch": "^3.2.0",
|
"@git.zone/tswatch": "^3.2.5",
|
||||||
"@types/node": "^25.3.3"
|
"@types/node": "^25.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest": "^3.2.7",
|
"@api.global/typedrequest": "^3.3.0",
|
||||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||||
"@api.global/typedserver": "^8.4.0",
|
"@api.global/typedserver": "^8.4.2",
|
||||||
"@api.global/typedsocket": "^4.1.2",
|
"@api.global/typedsocket": "^4.1.2",
|
||||||
"@apiclient.xyz/cloudflare": "^7.1.0",
|
"@apiclient.xyz/cloudflare": "^7.1.0",
|
||||||
"@design.estate/dees-catalog": "^3.43.3",
|
"@design.estate/dees-catalog": "^3.43.3",
|
||||||
"@design.estate/dees-element": "^2.1.6",
|
"@design.estate/dees-element": "^2.1.6",
|
||||||
|
"@push.rocks/lik": "^6.3.1",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/qenv": "^6.1.3",
|
"@push.rocks/qenv": "^6.1.3",
|
||||||
"@push.rocks/smartacme": "^9.1.3",
|
"@push.rocks/smartacme": "^9.1.3",
|
||||||
@@ -99,10 +101,12 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
"ts_web/**/*",
|
"ts_web/**/*",
|
||||||
|
"ts_apiclient/**/*",
|
||||||
"dist/**/*",
|
"dist/**/*",
|
||||||
"dist_*/**/*",
|
"dist_*/**/*",
|
||||||
"dist_ts/**/*",
|
"dist_ts/**/*",
|
||||||
"dist_ts_web/**/*",
|
"dist_ts_web/**/*",
|
||||||
|
"dist_ts_apiclient/**/*",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"cli.js",
|
"cli.js",
|
||||||
"npmextra.json",
|
"npmextra.json",
|
||||||
|
|||||||
2748
pnpm-lock.yaml
generated
2748
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
376
test/test.apiclient.ts
Normal file
376
test/test.apiclient.ts
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import {
|
||||||
|
DcRouterApiClient,
|
||||||
|
Route,
|
||||||
|
RouteBuilder,
|
||||||
|
RouteManager,
|
||||||
|
Certificate,
|
||||||
|
CertificateManager,
|
||||||
|
ApiToken,
|
||||||
|
ApiTokenBuilder,
|
||||||
|
ApiTokenManager,
|
||||||
|
RemoteIngress,
|
||||||
|
RemoteIngressBuilder,
|
||||||
|
RemoteIngressManager,
|
||||||
|
Email,
|
||||||
|
EmailManager,
|
||||||
|
StatsManager,
|
||||||
|
ConfigManager,
|
||||||
|
LogManager,
|
||||||
|
RadiusManager,
|
||||||
|
RadiusClientManager,
|
||||||
|
RadiusVlanManager,
|
||||||
|
RadiusSessionManager,
|
||||||
|
} from '../ts_apiclient/index.js';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Instantiation & Structure
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - should instantiate with baseUrl', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
expect(client).toBeTruthy();
|
||||||
|
expect(client.baseUrl).toEqual('https://localhost:3000');
|
||||||
|
expect(client.identity).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - should strip trailing slashes from baseUrl', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000///' });
|
||||||
|
expect(client.baseUrl).toEqual('https://localhost:3000');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - should accept optional apiToken', async () => {
|
||||||
|
const client = new DcRouterApiClient({
|
||||||
|
baseUrl: 'https://localhost:3000',
|
||||||
|
apiToken: 'dcr_test_token',
|
||||||
|
});
|
||||||
|
expect(client.apiToken).toEqual('dcr_test_token');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - should have all resource managers', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
expect(client.routes).toBeInstanceOf(RouteManager);
|
||||||
|
expect(client.certificates).toBeInstanceOf(CertificateManager);
|
||||||
|
expect(client.apiTokens).toBeInstanceOf(ApiTokenManager);
|
||||||
|
expect(client.remoteIngress).toBeInstanceOf(RemoteIngressManager);
|
||||||
|
expect(client.stats).toBeInstanceOf(StatsManager);
|
||||||
|
expect(client.config).toBeInstanceOf(ConfigManager);
|
||||||
|
expect(client.logs).toBeInstanceOf(LogManager);
|
||||||
|
expect(client.emails).toBeInstanceOf(EmailManager);
|
||||||
|
expect(client.radius).toBeInstanceOf(RadiusManager);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// buildRequestPayload
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - buildRequestPayload includes identity when set', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const identity = {
|
||||||
|
jwt: 'test-jwt',
|
||||||
|
userId: 'user1',
|
||||||
|
name: 'Admin',
|
||||||
|
expiresAt: Date.now() + 3600000,
|
||||||
|
};
|
||||||
|
client.identity = identity;
|
||||||
|
|
||||||
|
const payload = client.buildRequestPayload({ extra: 'data' });
|
||||||
|
expect(payload.identity).toEqual(identity);
|
||||||
|
expect(payload.extra).toEqual('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - buildRequestPayload includes apiToken when set', async () => {
|
||||||
|
const client = new DcRouterApiClient({
|
||||||
|
baseUrl: 'https://localhost:3000',
|
||||||
|
apiToken: 'dcr_abc123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = client.buildRequestPayload();
|
||||||
|
expect(payload.apiToken).toEqual('dcr_abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('DcRouterApiClient - buildRequestPayload with both identity and apiToken', async () => {
|
||||||
|
const client = new DcRouterApiClient({
|
||||||
|
baseUrl: 'https://localhost:3000',
|
||||||
|
apiToken: 'dcr_abc123',
|
||||||
|
});
|
||||||
|
client.identity = {
|
||||||
|
jwt: 'test-jwt',
|
||||||
|
userId: 'user1',
|
||||||
|
name: 'Admin',
|
||||||
|
expiresAt: Date.now() + 3600000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const payload = client.buildRequestPayload({ foo: 'bar' });
|
||||||
|
expect(payload.identity).toBeTruthy();
|
||||||
|
expect(payload.apiToken).toEqual('dcr_abc123');
|
||||||
|
expect(payload.foo).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Route Builder
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('RouteBuilder - should support fluent builder pattern', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const builder = client.routes.build();
|
||||||
|
expect(builder).toBeInstanceOf(RouteBuilder);
|
||||||
|
|
||||||
|
// Fluent methods return `this` (same reference)
|
||||||
|
const result = builder
|
||||||
|
.setName('test-route')
|
||||||
|
.setMatch({ ports: 443, domains: 'example.com' })
|
||||||
|
.setAction({ type: 'forward', targets: [{ host: 'backend', port: 8080 }] })
|
||||||
|
.setEnabled(true);
|
||||||
|
|
||||||
|
expect(result === builder).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ApiToken Builder
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('ApiTokenBuilder - should support fluent builder pattern', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const builder = client.apiTokens.build();
|
||||||
|
expect(builder).toBeInstanceOf(ApiTokenBuilder);
|
||||||
|
|
||||||
|
const result = builder
|
||||||
|
.setName('ci-token')
|
||||||
|
.setScopes(['routes:read', 'routes:write'])
|
||||||
|
.addScope('config:read')
|
||||||
|
.setExpiresInDays(30);
|
||||||
|
|
||||||
|
expect(result === builder).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// RemoteIngress Builder
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('RemoteIngressBuilder - should support fluent builder pattern', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const builder = client.remoteIngress.build();
|
||||||
|
expect(builder).toBeInstanceOf(RemoteIngressBuilder);
|
||||||
|
|
||||||
|
const result = builder
|
||||||
|
.setName('edge-1')
|
||||||
|
.setListenPorts([80, 443])
|
||||||
|
.setAutoDerivePorts(true)
|
||||||
|
.setTags(['production']);
|
||||||
|
|
||||||
|
expect(result === builder).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Route resource class
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('Route - should hydrate from IMergedRoute data', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const route = new Route(client, {
|
||||||
|
route: {
|
||||||
|
name: 'test-route',
|
||||||
|
match: { ports: 443, domains: 'example.com' },
|
||||||
|
action: { type: 'forward', targets: [{ host: 'backend', port: 8080 }] },
|
||||||
|
},
|
||||||
|
source: 'programmatic',
|
||||||
|
enabled: true,
|
||||||
|
overridden: false,
|
||||||
|
storedRouteId: 'route-123',
|
||||||
|
createdAt: 1000,
|
||||||
|
updatedAt: 2000,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(route.name).toEqual('test-route');
|
||||||
|
expect(route.source).toEqual('programmatic');
|
||||||
|
expect(route.enabled).toEqual(true);
|
||||||
|
expect(route.overridden).toEqual(false);
|
||||||
|
expect(route.storedRouteId).toEqual('route-123');
|
||||||
|
expect(route.routeConfig.match.ports).toEqual(443);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Route - should throw on update/delete/toggle for hardcoded routes', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const route = new Route(client, {
|
||||||
|
route: {
|
||||||
|
name: 'hardcoded-route',
|
||||||
|
match: { ports: 80 },
|
||||||
|
action: { type: 'forward', targets: [{ host: 'localhost', port: 8080 }] },
|
||||||
|
},
|
||||||
|
source: 'hardcoded',
|
||||||
|
enabled: true,
|
||||||
|
overridden: false,
|
||||||
|
// No storedRouteId for hardcoded routes
|
||||||
|
});
|
||||||
|
|
||||||
|
let updateError: Error | undefined;
|
||||||
|
try {
|
||||||
|
await route.update({ name: 'new-name' });
|
||||||
|
} catch (e) {
|
||||||
|
updateError = e as Error;
|
||||||
|
}
|
||||||
|
expect(updateError).toBeTruthy();
|
||||||
|
expect(updateError!.message).toInclude('hardcoded');
|
||||||
|
|
||||||
|
let deleteError: Error | undefined;
|
||||||
|
try {
|
||||||
|
await route.delete();
|
||||||
|
} catch (e) {
|
||||||
|
deleteError = e as Error;
|
||||||
|
}
|
||||||
|
expect(deleteError).toBeTruthy();
|
||||||
|
|
||||||
|
let toggleError: Error | undefined;
|
||||||
|
try {
|
||||||
|
await route.toggle(false);
|
||||||
|
} catch (e) {
|
||||||
|
toggleError = e as Error;
|
||||||
|
}
|
||||||
|
expect(toggleError).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Certificate resource class
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('Certificate - should hydrate from ICertificateInfo data', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const cert = new Certificate(client, {
|
||||||
|
domain: 'example.com',
|
||||||
|
routeNames: ['main-route'],
|
||||||
|
status: 'valid',
|
||||||
|
source: 'acme',
|
||||||
|
tlsMode: 'terminate',
|
||||||
|
expiryDate: '2027-01-01T00:00:00Z',
|
||||||
|
issuer: "Let's Encrypt",
|
||||||
|
canReprovision: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(cert.domain).toEqual('example.com');
|
||||||
|
expect(cert.status).toEqual('valid');
|
||||||
|
expect(cert.source).toEqual('acme');
|
||||||
|
expect(cert.canReprovision).toEqual(true);
|
||||||
|
expect(cert.routeNames.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// ApiToken resource class
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('ApiToken - should hydrate from IApiTokenInfo data', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const token = new ApiToken(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
id: 'token-1',
|
||||||
|
name: 'ci-token',
|
||||||
|
scopes: ['routes:read', 'routes:write'],
|
||||||
|
createdAt: Date.now(),
|
||||||
|
expiresAt: null,
|
||||||
|
lastUsedAt: null,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
'dcr_secret_value',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(token.id).toEqual('token-1');
|
||||||
|
expect(token.name).toEqual('ci-token');
|
||||||
|
expect(token.scopes.length).toEqual(2);
|
||||||
|
expect(token.enabled).toEqual(true);
|
||||||
|
expect(token.tokenValue).toEqual('dcr_secret_value');
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// RemoteIngress resource class
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('RemoteIngress - should hydrate from IRemoteIngress data', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const edge = new RemoteIngress(client, {
|
||||||
|
id: 'edge-1',
|
||||||
|
name: 'test-edge',
|
||||||
|
secret: 'secret123',
|
||||||
|
listenPorts: [80, 443],
|
||||||
|
enabled: true,
|
||||||
|
autoDerivePorts: true,
|
||||||
|
tags: ['prod'],
|
||||||
|
createdAt: 1000,
|
||||||
|
updatedAt: 2000,
|
||||||
|
effectiveListenPorts: [80, 443, 8080],
|
||||||
|
manualPorts: [80, 443],
|
||||||
|
derivedPorts: [8080],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(edge.id).toEqual('edge-1');
|
||||||
|
expect(edge.name).toEqual('test-edge');
|
||||||
|
expect(edge.listenPorts.length).toEqual(2);
|
||||||
|
expect(edge.effectiveListenPorts!.length).toEqual(3);
|
||||||
|
expect(edge.autoDerivePorts).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Email resource class
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('Email - should hydrate from IEmail data', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
const email = new Email(client, {
|
||||||
|
id: 'email-1',
|
||||||
|
direction: 'inbound',
|
||||||
|
status: 'delivered',
|
||||||
|
from: 'sender@example.com',
|
||||||
|
to: 'recipient@example.com',
|
||||||
|
subject: 'Test email',
|
||||||
|
timestamp: '2026-03-06T00:00:00Z',
|
||||||
|
messageId: '<msg-1@example.com>',
|
||||||
|
size: '1234',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(email.id).toEqual('email-1');
|
||||||
|
expect(email.direction).toEqual('inbound');
|
||||||
|
expect(email.status).toEqual('delivered');
|
||||||
|
expect(email.from).toEqual('sender@example.com');
|
||||||
|
expect(email.subject).toEqual('Test email');
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// RadiusManager structure
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('RadiusManager - should have sub-managers', async () => {
|
||||||
|
const client = new DcRouterApiClient({ baseUrl: 'https://localhost:3000' });
|
||||||
|
expect(client.radius.clients).toBeInstanceOf(RadiusClientManager);
|
||||||
|
expect(client.radius.vlans).toBeInstanceOf(RadiusVlanManager);
|
||||||
|
expect(client.radius.sessions).toBeInstanceOf(RadiusSessionManager);
|
||||||
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Exports verification
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
tap.test('Exports - all expected classes should be importable', async () => {
|
||||||
|
expect(DcRouterApiClient).toBeTruthy();
|
||||||
|
expect(Route).toBeTruthy();
|
||||||
|
expect(RouteBuilder).toBeTruthy();
|
||||||
|
expect(RouteManager).toBeTruthy();
|
||||||
|
expect(Certificate).toBeTruthy();
|
||||||
|
expect(CertificateManager).toBeTruthy();
|
||||||
|
expect(ApiToken).toBeTruthy();
|
||||||
|
expect(ApiTokenBuilder).toBeTruthy();
|
||||||
|
expect(ApiTokenManager).toBeTruthy();
|
||||||
|
expect(RemoteIngress).toBeTruthy();
|
||||||
|
expect(RemoteIngressBuilder).toBeTruthy();
|
||||||
|
expect(RemoteIngressManager).toBeTruthy();
|
||||||
|
expect(Email).toBeTruthy();
|
||||||
|
expect(EmailManager).toBeTruthy();
|
||||||
|
expect(StatsManager).toBeTruthy();
|
||||||
|
expect(ConfigManager).toBeTruthy();
|
||||||
|
expect(LogManager).toBeTruthy();
|
||||||
|
expect(RadiusManager).toBeTruthy();
|
||||||
|
expect(RadiusClientManager).toBeTruthy();
|
||||||
|
expect(RadiusVlanManager).toBeTruthy();
|
||||||
|
expect(RadiusSessionManager).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -4,27 +4,44 @@ import { TypedRequest } from '@api.global/typedrequest';
|
|||||||
import * as interfaces from '../ts_interfaces/index.js';
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
|
||||||
let testDcRouter: DcRouter;
|
let testDcRouter: DcRouter;
|
||||||
|
let adminIdentity: interfaces.data.IIdentity;
|
||||||
|
|
||||||
tap.test('should start DCRouter with OpsServer', async () => {
|
tap.test('should start DCRouter with OpsServer', async () => {
|
||||||
testDcRouter = new DcRouter({
|
testDcRouter = new DcRouter({
|
||||||
// Minimal config for testing
|
// Minimal config for testing
|
||||||
cacheConfig: { enabled: false },
|
cacheConfig: { enabled: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
await testDcRouter.start();
|
await testDcRouter.start();
|
||||||
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
|
expect(testDcRouter.opsServer).toBeInstanceOf(Object);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tap.test('should login as admin', async () => {
|
||||||
|
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
|
||||||
|
'http://localhost:3000/typedrequest',
|
||||||
|
'adminLoginWithUsernameAndPassword'
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await loginRequest.fire({
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response).toHaveProperty('identity');
|
||||||
|
adminIdentity = response.identity;
|
||||||
|
});
|
||||||
|
|
||||||
tap.test('should respond to health status request', async () => {
|
tap.test('should respond to health status request', async () => {
|
||||||
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
|
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
|
||||||
'http://localhost:3000/typedrequest',
|
'http://localhost:3000/typedrequest',
|
||||||
'getHealthStatus'
|
'getHealthStatus'
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await healthRequest.fire({
|
const response = await healthRequest.fire({
|
||||||
detailed: false
|
identity: adminIdentity,
|
||||||
|
detailed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response).toHaveProperty('health');
|
expect(response).toHaveProperty('health');
|
||||||
expect(response.health.healthy).toBeTrue();
|
expect(response.health.healthy).toBeTrue();
|
||||||
expect(response.health.services).toHaveProperty('OpsServer');
|
expect(response.health.services).toHaveProperty('OpsServer');
|
||||||
@@ -35,11 +52,12 @@ tap.test('should respond to server statistics request', async () => {
|
|||||||
'http://localhost:3000/typedrequest',
|
'http://localhost:3000/typedrequest',
|
||||||
'getServerStatistics'
|
'getServerStatistics'
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await statsRequest.fire({
|
const response = await statsRequest.fire({
|
||||||
includeHistory: false
|
identity: adminIdentity,
|
||||||
|
includeHistory: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response).toHaveProperty('stats');
|
expect(response).toHaveProperty('stats');
|
||||||
expect(response.stats).toHaveProperty('uptime');
|
expect(response.stats).toHaveProperty('uptime');
|
||||||
expect(response.stats).toHaveProperty('cpuUsage');
|
expect(response.stats).toHaveProperty('cpuUsage');
|
||||||
@@ -51,9 +69,11 @@ tap.test('should respond to configuration request', async () => {
|
|||||||
'http://localhost:3000/typedrequest',
|
'http://localhost:3000/typedrequest',
|
||||||
'getConfiguration'
|
'getConfiguration'
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await configRequest.fire({});
|
const response = await configRequest.fire({
|
||||||
|
identity: adminIdentity,
|
||||||
|
});
|
||||||
|
|
||||||
expect(response).toHaveProperty('config');
|
expect(response).toHaveProperty('config');
|
||||||
expect(response.config).toHaveProperty('system');
|
expect(response.config).toHaveProperty('system');
|
||||||
expect(response.config).toHaveProperty('smartProxy');
|
expect(response.config).toHaveProperty('smartProxy');
|
||||||
@@ -70,19 +90,34 @@ tap.test('should handle log retrieval request', async () => {
|
|||||||
'http://localhost:3000/typedrequest',
|
'http://localhost:3000/typedrequest',
|
||||||
'getRecentLogs'
|
'getRecentLogs'
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await logsRequest.fire({
|
const response = await logsRequest.fire({
|
||||||
limit: 10
|
identity: adminIdentity,
|
||||||
|
limit: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response).toHaveProperty('logs');
|
expect(response).toHaveProperty('logs');
|
||||||
expect(response).toHaveProperty('total');
|
expect(response).toHaveProperty('total');
|
||||||
expect(response).toHaveProperty('hasMore');
|
expect(response).toHaveProperty('hasMore');
|
||||||
expect(response.logs).toBeArray();
|
expect(response.logs).toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tap.test('should reject unauthenticated requests', async () => {
|
||||||
|
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
|
||||||
|
'http://localhost:3000/typedrequest',
|
||||||
|
'getHealthStatus'
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await healthRequest.fire({} as any);
|
||||||
|
expect(true).toBeFalse(); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tap.test('should stop DCRouter', async () => {
|
tap.test('should stop DCRouter', async () => {
|
||||||
await testDcRouter.stop();
|
await testDcRouter.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
||||||
|
|||||||
@@ -82,28 +82,31 @@ tap.test('should reject verify identity with invalid JWT', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should allow access to public endpoints without auth', async () => {
|
tap.test('should reject protected endpoints without auth', async () => {
|
||||||
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
|
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
|
||||||
'http://localhost:3000/typedrequest',
|
'http://localhost:3000/typedrequest',
|
||||||
'getHealthStatus'
|
'getHealthStatus'
|
||||||
);
|
);
|
||||||
|
|
||||||
// No identity provided
|
try {
|
||||||
const response = await healthRequest.fire({});
|
// No identity provided — should be rejected
|
||||||
|
await healthRequest.fire({} as any);
|
||||||
expect(response).toHaveProperty('health');
|
expect(true).toBeFalse(); // Should not reach here
|
||||||
expect(response.health.healthy).toBeTrue();
|
} catch (error) {
|
||||||
console.log('Public endpoint accessible without auth');
|
expect(error).toBeTruthy();
|
||||||
|
console.log('Protected endpoint correctly rejects unauthenticated request');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should allow read-only config access', async () => {
|
tap.test('should allow authenticated access to protected endpoints', async () => {
|
||||||
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
|
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
|
||||||
'http://localhost:3000/typedrequest',
|
'http://localhost:3000/typedrequest',
|
||||||
'getConfiguration'
|
'getConfiguration'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Config is read-only and doesn't require auth
|
const response = await configRequest.fire({
|
||||||
const response = await configRequest.fire({});
|
identity: adminIdentity,
|
||||||
|
});
|
||||||
|
|
||||||
expect(response).toHaveProperty('config');
|
expect(response).toHaveProperty('config');
|
||||||
expect(response.config).toHaveProperty('system');
|
expect(response.config).toHaveProperty('system');
|
||||||
@@ -114,7 +117,7 @@ tap.test('should allow read-only config access', async () => {
|
|||||||
expect(response.config).toHaveProperty('cache');
|
expect(response.config).toHaveProperty('cache');
|
||||||
expect(response.config).toHaveProperty('radius');
|
expect(response.config).toHaveProperty('radius');
|
||||||
expect(response.config).toHaveProperty('remoteIngress');
|
expect(response.config).toHaveProperty('remoteIngress');
|
||||||
console.log('Configuration read successfully');
|
console.log('Authenticated access to config successful');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should stop DCRouter', async () => {
|
tap.test('should stop DCRouter', async () => {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '10.1.9',
|
version: '11.1.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ import type DcRouter from '../classes.dcrouter.js';
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
import * as handlers from './handlers/index.js';
|
import * as handlers from './handlers/index.js';
|
||||||
|
import * as interfaces from '../../ts_interfaces/index.js';
|
||||||
|
import { requireValidIdentity, requireAdminIdentity } from './helpers/guards.js';
|
||||||
|
|
||||||
export class OpsServer {
|
export class OpsServer {
|
||||||
public dcRouterRef: DcRouter;
|
public dcRouterRef: DcRouter;
|
||||||
public server: plugins.typedserver.utilityservers.UtilityWebsiteServer;
|
public server: plugins.typedserver.utilityservers.UtilityWebsiteServer;
|
||||||
|
|
||||||
// TypedRouter for OpsServer-specific handlers
|
// Main TypedRouter — unauthenticated endpoints (login/logout/verify) and own-auth handlers
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
// Auth-enforced routers — middleware validates identity before any handler runs
|
||||||
|
public viewRouter = new plugins.typedrequest.TypedRouter<{ request: { identity: interfaces.data.IIdentity } }>();
|
||||||
|
public adminRouter = new plugins.typedrequest.TypedRouter<{ request: { identity: interfaces.data.IIdentity } }>();
|
||||||
|
|
||||||
// Handler instances
|
// Handler instances
|
||||||
public adminHandler: handlers.AdminHandler;
|
public adminHandler: handlers.AdminHandler;
|
||||||
private configHandler: handlers.ConfigHandler;
|
private configHandler: handlers.ConfigHandler;
|
||||||
@@ -25,7 +31,7 @@ export class OpsServer {
|
|||||||
|
|
||||||
constructor(dcRouterRefArg: DcRouter) {
|
constructor(dcRouterRefArg: DcRouter) {
|
||||||
this.dcRouterRef = dcRouterRefArg;
|
this.dcRouterRef = dcRouterRefArg;
|
||||||
|
|
||||||
// Add our typedrouter to the dcRouter's main typedrouter
|
// Add our typedrouter to the dcRouter's main typedrouter
|
||||||
this.dcRouterRef.typedrouter.addTypedRouter(this.typedrouter);
|
this.dcRouterRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
}
|
}
|
||||||
@@ -51,10 +57,25 @@ export class OpsServer {
|
|||||||
* Set up all TypedRequest handlers
|
* Set up all TypedRequest handlers
|
||||||
*/
|
*/
|
||||||
private async setupHandlers(): Promise<void> {
|
private async setupHandlers(): Promise<void> {
|
||||||
// Instantiate all handlers - they self-register with the typedrouter
|
// AdminHandler must be initialized first (JWT setup needed for guards)
|
||||||
this.adminHandler = new handlers.AdminHandler(this);
|
this.adminHandler = new handlers.AdminHandler(this);
|
||||||
await this.adminHandler.initialize(); // JWT needs async initialization
|
await this.adminHandler.initialize();
|
||||||
|
|
||||||
|
// viewRouter middleware: requires valid identity (any logged-in user)
|
||||||
|
this.viewRouter.addMiddleware(async (typedRequest) => {
|
||||||
|
await requireValidIdentity(this.adminHandler, typedRequest.request);
|
||||||
|
});
|
||||||
|
|
||||||
|
// adminRouter middleware: requires admin identity
|
||||||
|
this.adminRouter.addMiddleware(async (typedRequest) => {
|
||||||
|
await requireAdminIdentity(this.adminHandler, typedRequest.request);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect auth routers to the main typedrouter
|
||||||
|
this.typedrouter.addTypedRouter(this.viewRouter);
|
||||||
|
this.typedrouter.addTypedRouter(this.adminRouter);
|
||||||
|
|
||||||
|
// Instantiate all handlers — they self-register with the appropriate router
|
||||||
this.configHandler = new handlers.ConfigHandler(this);
|
this.configHandler = new handlers.ConfigHandler(this);
|
||||||
this.logsHandler = new handlers.LogsHandler(this);
|
this.logsHandler = new handlers.LogsHandler(this);
|
||||||
this.securityHandler = new handlers.SecurityHandler(this);
|
this.securityHandler = new handlers.SecurityHandler(this);
|
||||||
|
|||||||
@@ -3,34 +3,20 @@ import type { OpsServer } from '../classes.opsserver.js';
|
|||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
|
||||||
export class ApiTokenHandler {
|
export class ApiTokenHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Token management requires admin JWT only (tokens cannot manage tokens).
|
|
||||||
*/
|
|
||||||
private async requireAdmin(identity?: interfaces.data.IIdentity): Promise<string> {
|
|
||||||
if (!identity?.jwt) {
|
|
||||||
throw new plugins.typedrequest.TypedResponseError('unauthorized');
|
|
||||||
}
|
|
||||||
const isAdmin = await this.opsServerRef.adminHandler.adminIdentityGuard.exec({ identity });
|
|
||||||
if (!isAdmin) {
|
|
||||||
throw new plugins.typedrequest.TypedResponseError('admin access required');
|
|
||||||
}
|
|
||||||
return identity.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
// All token management endpoints register directly on adminRouter
|
||||||
|
// (middleware enforces admin JWT check, so no per-handler requireAdmin needed)
|
||||||
|
const router = this.opsServerRef.adminRouter;
|
||||||
|
|
||||||
// Create API token
|
// Create API token
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateApiToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateApiToken>(
|
||||||
'createApiToken',
|
'createApiToken',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
const userId = await this.requireAdmin(dataArg.identity);
|
|
||||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return { success: false, message: 'Token management not initialized' };
|
return { success: false, message: 'Token management not initialized' };
|
||||||
@@ -39,7 +25,7 @@ export class ApiTokenHandler {
|
|||||||
dataArg.name,
|
dataArg.name,
|
||||||
dataArg.scopes,
|
dataArg.scopes,
|
||||||
dataArg.expiresInDays ?? null,
|
dataArg.expiresInDays ?? null,
|
||||||
userId,
|
dataArg.identity.userId,
|
||||||
);
|
);
|
||||||
return { success: true, tokenId: result.id, tokenValue: result.rawToken };
|
return { success: true, tokenId: result.id, tokenValue: result.rawToken };
|
||||||
},
|
},
|
||||||
@@ -47,11 +33,10 @@ export class ApiTokenHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// List API tokens
|
// List API tokens
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListApiTokens>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListApiTokens>(
|
||||||
'listApiTokens',
|
'listApiTokens',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await this.requireAdmin(dataArg.identity);
|
|
||||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return { tokens: [] };
|
return { tokens: [] };
|
||||||
@@ -62,11 +47,10 @@ export class ApiTokenHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Revoke API token
|
// Revoke API token
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RevokeApiToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RevokeApiToken>(
|
||||||
'revokeApiToken',
|
'revokeApiToken',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await this.requireAdmin(dataArg.identity);
|
|
||||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return { success: false, message: 'Token management not initialized' };
|
return { success: false, message: 'Token management not initialized' };
|
||||||
@@ -78,11 +62,10 @@ export class ApiTokenHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Roll API token
|
// Roll API token
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>(
|
||||||
'rollApiToken',
|
'rollApiToken',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await this.requireAdmin(dataArg.identity);
|
|
||||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return { success: false, message: 'Token management not initialized' };
|
return { success: false, message: 'Token management not initialized' };
|
||||||
@@ -97,11 +80,10 @@ export class ApiTokenHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Toggle API token
|
// Toggle API token
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
|
||||||
'toggleApiToken',
|
'toggleApiToken',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
await this.requireAdmin(dataArg.identity);
|
|
||||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
return { success: false, message: 'Token management not initialized' };
|
return { success: false, message: 'Token management not initialized' };
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
|
|||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
|
||||||
export class CertificateHandler {
|
export class CertificateHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
const viewRouter = this.opsServerRef.viewRouter;
|
||||||
|
const adminRouter = this.opsServerRef.adminRouter;
|
||||||
|
|
||||||
|
// ---- Read endpoints (viewRouter — valid identity required via middleware) ----
|
||||||
|
|
||||||
// Get Certificate Overview
|
// Get Certificate Overview
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>(
|
||||||
'getCertificateOverview',
|
'getCertificateOverview',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -23,8 +25,10 @@ export class CertificateHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ---- Write endpoints (adminRouter — admin identity required via middleware) ----
|
||||||
|
|
||||||
// Legacy route-based reprovision (backward compat)
|
// Legacy route-based reprovision (backward compat)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificate>(
|
||||||
'reprovisionCertificate',
|
'reprovisionCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -34,7 +38,7 @@ export class CertificateHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Domain-based reprovision (preferred)
|
// Domain-based reprovision (preferred)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>(
|
||||||
'reprovisionCertificateDomain',
|
'reprovisionCertificateDomain',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -44,7 +48,7 @@ export class CertificateHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Delete certificate
|
// Delete certificate
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteCertificate>(
|
||||||
'deleteCertificate',
|
'deleteCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -54,7 +58,7 @@ export class CertificateHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Export certificate
|
// Export certificate
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportCertificate>(
|
||||||
'exportCertificate',
|
'exportCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -64,7 +68,7 @@ export class CertificateHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Import certificate
|
// Import certificate
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportCertificate>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportCertificate>(
|
||||||
'importCertificate',
|
'importCertificate',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import type { OpsServer } from '../classes.opsserver.js';
|
|||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
|
||||||
export class ConfigHandler {
|
export class ConfigHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
// Add this handler's router to the parent
|
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
// Config endpoint registers directly on viewRouter (valid identity required via middleware)
|
||||||
|
const router = this.opsServerRef.viewRouter;
|
||||||
|
|
||||||
// Get Configuration Handler (read-only)
|
// Get Configuration Handler (read-only)
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>(
|
||||||
'getConfiguration',
|
'getConfiguration',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
|
|||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
|
||||||
export class EmailOpsHandler {
|
export class EmailOpsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
// Add this handler's router to the parent
|
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
const viewRouter = this.opsServerRef.viewRouter;
|
||||||
|
const adminRouter = this.opsServerRef.adminRouter;
|
||||||
|
|
||||||
|
// ---- Read endpoints (viewRouter — valid identity required via middleware) ----
|
||||||
|
|
||||||
// Get All Emails Handler
|
// Get All Emails Handler
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>(
|
||||||
'getAllEmails',
|
'getAllEmails',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -24,7 +25,7 @@ export class EmailOpsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Get Email Detail Handler
|
// Get Email Detail Handler
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>(
|
||||||
'getEmailDetail',
|
'getEmailDetail',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
@@ -34,8 +35,10 @@ export class EmailOpsHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ---- Write endpoints (adminRouter) ----
|
||||||
|
|
||||||
// Resend Failed Email Handler
|
// Resend Failed Email Handler
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ResendEmail>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ResendEmail>(
|
||||||
'resendEmail',
|
'resendEmail',
|
||||||
async (dataArg) => {
|
async (dataArg) => {
|
||||||
|
|||||||
@@ -10,12 +10,9 @@ let logPushDestinationInstalled = false;
|
|||||||
let currentOpsServerRef: OpsServer | null = null;
|
let currentOpsServerRef: OpsServer | null = null;
|
||||||
|
|
||||||
export class LogsHandler {
|
export class LogsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
private activeStreamStops: Set<() => void> = new Set();
|
private activeStreamStops: Set<() => void> = new Set();
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
// Add this handler's router to the parent
|
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
this.setupLogPushDestination();
|
this.setupLogPushDestination();
|
||||||
}
|
}
|
||||||
@@ -35,8 +32,11 @@ export class LogsHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
// All log endpoints register directly on viewRouter (valid identity required via middleware)
|
||||||
|
const router = this.opsServerRef.viewRouter;
|
||||||
|
|
||||||
// Get Recent Logs Handler
|
// Get Recent Logs Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRecentLogs>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRecentLogs>(
|
||||||
'getRecentLogs',
|
'getRecentLogs',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -59,7 +59,7 @@ export class LogsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Get Log Stream Handler
|
// Get Log Stream Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetLogStream>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetLogStream>(
|
||||||
'getLogStream',
|
'getLogStream',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
|||||||
@@ -3,21 +3,19 @@ import type { OpsServer } from '../classes.opsserver.js';
|
|||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
|
||||||
export class RadiusHandler {
|
export class RadiusHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
// Add this handler's router to the parent
|
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
const viewRouter = this.opsServerRef.viewRouter;
|
||||||
|
const adminRouter = this.opsServerRef.adminRouter;
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// RADIUS Client Management
|
// RADIUS Client Management
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
// Get all RADIUS clients
|
// Get all RADIUS clients (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusClients>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusClients>(
|
||||||
'getRadiusClients',
|
'getRadiusClients',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -40,8 +38,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add or update a RADIUS client
|
// Add or update a RADIUS client (write)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRadiusClient>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRadiusClient>(
|
||||||
'setRadiusClient',
|
'setRadiusClient',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -61,8 +59,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove a RADIUS client
|
// Remove a RADIUS client (write)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRadiusClient>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRadiusClient>(
|
||||||
'removeRadiusClient',
|
'removeRadiusClient',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -85,8 +83,8 @@ export class RadiusHandler {
|
|||||||
// VLAN Mapping Management
|
// VLAN Mapping Management
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
// Get all VLAN mappings
|
// Get all VLAN mappings (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVlanMappings>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVlanMappings>(
|
||||||
'getVlanMappings',
|
'getVlanMappings',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -121,8 +119,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add or update a VLAN mapping
|
// Add or update a VLAN mapping (write)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetVlanMapping>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetVlanMapping>(
|
||||||
'setVlanMapping',
|
'setVlanMapping',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -153,8 +151,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove a VLAN mapping
|
// Remove a VLAN mapping (write)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveVlanMapping>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveVlanMapping>(
|
||||||
'removeVlanMapping',
|
'removeVlanMapping',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -174,8 +172,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update VLAN configuration
|
// Update VLAN configuration (write)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVlanConfig>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVlanConfig>(
|
||||||
'updateVlanConfig',
|
'updateVlanConfig',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -206,8 +204,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test VLAN assignment
|
// Test VLAN assignment (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestVlanAssignment>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestVlanAssignment>(
|
||||||
'testVlanAssignment',
|
'testVlanAssignment',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -240,8 +238,8 @@ export class RadiusHandler {
|
|||||||
// Accounting / Session Management
|
// Accounting / Session Management
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
// Get active sessions
|
// Get active sessions (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusSessions>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusSessions>(
|
||||||
'getRadiusSessions',
|
'getRadiusSessions',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -289,8 +287,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Disconnect a session
|
// Disconnect a session (write)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisconnectRadiusSession>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisconnectRadiusSession>(
|
||||||
'disconnectRadiusSession',
|
'disconnectRadiusSession',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -314,8 +312,8 @@ export class RadiusHandler {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get accounting summary
|
// Get accounting summary (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusAccountingSummary>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusAccountingSummary>(
|
||||||
'getRadiusAccountingSummary',
|
'getRadiusAccountingSummary',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -351,8 +349,8 @@ export class RadiusHandler {
|
|||||||
// Statistics
|
// Statistics
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
// Get RADIUS statistics
|
// Get RADIUS statistics (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusStatistics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusStatistics>(
|
||||||
'getRadiusStatistics',
|
'getRadiusStatistics',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
|
|||||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||||
|
|
||||||
export class RemoteIngressHandler {
|
export class RemoteIngressHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
const viewRouter = this.opsServerRef.viewRouter;
|
||||||
|
const adminRouter = this.opsServerRef.adminRouter;
|
||||||
|
|
||||||
|
// ---- Read endpoints (viewRouter — valid identity required via middleware) ----
|
||||||
|
|
||||||
// Get all remote ingress edges
|
// Get all remote ingress edges
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>(
|
||||||
'getRemoteIngresses',
|
'getRemoteIngresses',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -36,8 +38,10 @@ export class RemoteIngressHandler {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ---- Write endpoints (adminRouter) ----
|
||||||
|
|
||||||
// Create a new remote ingress edge
|
// Create a new remote ingress edge
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>(
|
||||||
'createRemoteIngress',
|
'createRemoteIngress',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -69,7 +73,7 @@ export class RemoteIngressHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Delete a remote ingress edge
|
// Delete a remote ingress edge
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>(
|
||||||
'deleteRemoteIngress',
|
'deleteRemoteIngress',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -94,7 +98,7 @@ export class RemoteIngressHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update a remote ingress edge
|
// Update a remote ingress edge
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>(
|
||||||
'updateRemoteIngress',
|
'updateRemoteIngress',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -138,7 +142,7 @@ export class RemoteIngressHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Regenerate secret for an edge
|
// Regenerate secret for an edge
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
|
||||||
'regenerateRemoteIngressSecret',
|
'regenerateRemoteIngressSecret',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -164,8 +168,8 @@ export class RemoteIngressHandler {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get runtime status of all edges
|
// Get runtime status of all edges (read)
|
||||||
this.typedrouter.addTypedHandler(
|
viewRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>(
|
||||||
'getRemoteIngressStatus',
|
'getRemoteIngressStatus',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -178,8 +182,8 @@ export class RemoteIngressHandler {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get a connection token for an edge
|
// Get a connection token for an edge (write — exposes secret)
|
||||||
this.typedrouter.addTypedHandler(
|
adminRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressConnectionToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressConnectionToken>(
|
||||||
'getRemoteIngressConnectionToken',
|
'getRemoteIngressConnectionToken',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import * as interfaces from '../../../ts_interfaces/index.js';
|
|||||||
import { MetricsManager } from '../../monitoring/index.js';
|
import { MetricsManager } from '../../monitoring/index.js';
|
||||||
|
|
||||||
export class SecurityHandler {
|
export class SecurityHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
// Add this handler's router to the parent
|
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
// All security endpoints register directly on viewRouter (valid identity required via middleware)
|
||||||
|
const router = this.opsServerRef.viewRouter;
|
||||||
|
|
||||||
// Security Metrics Handler
|
// Security Metrics Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>(
|
||||||
'getSecurityMetrics',
|
'getSecurityMetrics',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -40,7 +39,7 @@ export class SecurityHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Active Connections Handler
|
// Active Connections Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>(
|
||||||
'getActiveConnections',
|
'getActiveConnections',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -77,8 +76,8 @@ export class SecurityHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Network Stats Handler - provides comprehensive network metrics
|
// Network Stats Handler - provides comprehensive network metrics
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
|
||||||
'getNetworkStats',
|
'getNetworkStats',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
// Get network stats from MetricsManager if available
|
// Get network stats from MetricsManager if available
|
||||||
@@ -121,7 +120,7 @@ export class SecurityHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Rate Limit Status Handler
|
// Rate Limit Status Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>(
|
||||||
'getRateLimitStatus',
|
'getRateLimitStatus',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
|||||||
@@ -5,17 +5,16 @@ import { MetricsManager } from '../../monitoring/index.js';
|
|||||||
import { SecurityLogger } from '../../security/classes.securitylogger.js';
|
import { SecurityLogger } from '../../security/classes.securitylogger.js';
|
||||||
|
|
||||||
export class StatsHandler {
|
export class StatsHandler {
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
constructor(private opsServerRef: OpsServer) {
|
constructor(private opsServerRef: OpsServer) {
|
||||||
// Add this handler's router to the parent
|
|
||||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
||||||
this.registerHandlers();
|
this.registerHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerHandlers(): void {
|
private registerHandlers(): void {
|
||||||
|
// All stats endpoints register directly on viewRouter (valid identity required via middleware)
|
||||||
|
const router = this.opsServerRef.viewRouter;
|
||||||
|
|
||||||
// Server Statistics Handler
|
// Server Statistics Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>(
|
||||||
'getServerStatistics',
|
'getServerStatistics',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -38,7 +37,7 @@ export class StatsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Email Statistics Handler
|
// Email Statistics Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>(
|
||||||
'getEmailStatistics',
|
'getEmailStatistics',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -77,7 +76,7 @@ export class StatsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// DNS Statistics Handler
|
// DNS Statistics Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>(
|
||||||
'getDnsStatistics',
|
'getDnsStatistics',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -114,7 +113,7 @@ export class StatsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Queue Status Handler
|
// Queue Status Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>(
|
||||||
'getQueueStatus',
|
'getQueueStatus',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -142,7 +141,7 @@ export class StatsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Health Status Handler
|
// Health Status Handler
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>(
|
||||||
'getHealthStatus',
|
'getHealthStatus',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
@@ -167,7 +166,7 @@ export class StatsHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Combined Metrics Handler - More efficient for frontend polling
|
// Combined Metrics Handler - More efficient for frontend polling
|
||||||
this.typedrouter.addTypedHandler(
|
router.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCombinedMetrics>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCombinedMetrics>(
|
||||||
'getCombinedMetrics',
|
'getCombinedMetrics',
|
||||||
async (dataArg, toolsArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
|||||||
@@ -22,16 +22,17 @@ export async function passGuards<T extends { identity?: any }>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to check admin identity in handlers
|
* Helper to check admin identity in handlers and middleware.
|
||||||
|
* Accepts both optional and required identity for flexibility.
|
||||||
*/
|
*/
|
||||||
export async function requireAdminIdentity<T extends { identity?: interfaces.data.IIdentity }>(
|
export async function requireAdminIdentity(
|
||||||
adminHandler: AdminHandler,
|
adminHandler: AdminHandler,
|
||||||
dataArg: T
|
dataArg: { identity?: interfaces.data.IIdentity }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!dataArg.identity) {
|
if (!dataArg.identity) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const passed = await adminHandler.adminIdentityGuard.exec({ identity: dataArg.identity });
|
const passed = await adminHandler.adminIdentityGuard.exec({ identity: dataArg.identity });
|
||||||
if (!passed) {
|
if (!passed) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Admin access required');
|
throw new plugins.typedrequest.TypedResponseError('Admin access required');
|
||||||
@@ -39,16 +40,17 @@ export async function requireAdminIdentity<T extends { identity?: interfaces.dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to check valid identity in handlers
|
* Helper to check valid identity in handlers and middleware.
|
||||||
|
* Accepts both optional and required identity for flexibility.
|
||||||
*/
|
*/
|
||||||
export async function requireValidIdentity<T extends { identity?: interfaces.data.IIdentity }>(
|
export async function requireValidIdentity(
|
||||||
adminHandler: AdminHandler,
|
adminHandler: AdminHandler,
|
||||||
dataArg: T
|
dataArg: { identity?: interfaces.data.IIdentity }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!dataArg.identity) {
|
if (!dataArg.identity) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const passed = await adminHandler.validIdentityGuard.exec({ identity: dataArg.identity });
|
const passed = await adminHandler.validIdentityGuard.exec({ identity: dataArg.identity });
|
||||||
if (!passed) {
|
if (!passed) {
|
||||||
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
||||||
|
|||||||
157
ts_apiclient/classes.apitoken.ts
Normal file
157
ts_apiclient/classes.apitoken.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class ApiToken {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
// Data from IApiTokenInfo
|
||||||
|
public id: string;
|
||||||
|
public name: string;
|
||||||
|
public scopes: interfaces.data.TApiTokenScope[];
|
||||||
|
public createdAt: number;
|
||||||
|
public expiresAt: number | null;
|
||||||
|
public lastUsedAt: number | null;
|
||||||
|
public enabled: boolean;
|
||||||
|
|
||||||
|
/** Only set on creation or roll. Not persisted on server side. */
|
||||||
|
public tokenValue?: string;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient, data: interfaces.data.IApiTokenInfo, tokenValue?: string) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
this.id = data.id;
|
||||||
|
this.name = data.name;
|
||||||
|
this.scopes = data.scopes;
|
||||||
|
this.createdAt = data.createdAt;
|
||||||
|
this.expiresAt = data.expiresAt;
|
||||||
|
this.lastUsedAt = data.lastUsedAt;
|
||||||
|
this.enabled = data.enabled;
|
||||||
|
this.tokenValue = tokenValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async revoke(): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_RevokeApiToken>(
|
||||||
|
'revokeApiToken',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.id }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to revoke token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async roll(): Promise<string> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_RollApiToken>(
|
||||||
|
'rollApiToken',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.id }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to roll token');
|
||||||
|
}
|
||||||
|
this.tokenValue = response.tokenValue;
|
||||||
|
return response.tokenValue!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toggle(enabled: boolean): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ToggleApiToken>(
|
||||||
|
'toggleApiToken',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.id, enabled }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to toggle token');
|
||||||
|
}
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiTokenBuilder {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
private tokenName: string = '';
|
||||||
|
private tokenScopes: interfaces.data.TApiTokenScope[] = [];
|
||||||
|
private tokenExpiresInDays?: number | null;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setName(name: string): this {
|
||||||
|
this.tokenName = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setScopes(scopes: interfaces.data.TApiTokenScope[]): this {
|
||||||
|
this.tokenScopes = scopes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addScope(scope: interfaces.data.TApiTokenScope): this {
|
||||||
|
if (!this.tokenScopes.includes(scope)) {
|
||||||
|
this.tokenScopes.push(scope);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setExpiresInDays(days: number | null): this {
|
||||||
|
this.tokenExpiresInDays = days;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(): Promise<ApiToken> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_CreateApiToken>(
|
||||||
|
'createApiToken',
|
||||||
|
this.clientRef.buildRequestPayload({
|
||||||
|
name: this.tokenName,
|
||||||
|
scopes: this.tokenScopes,
|
||||||
|
expiresInDays: this.tokenExpiresInDays,
|
||||||
|
}) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to create API token');
|
||||||
|
}
|
||||||
|
return new ApiToken(
|
||||||
|
this.clientRef,
|
||||||
|
{
|
||||||
|
id: response.tokenId!,
|
||||||
|
name: this.tokenName,
|
||||||
|
scopes: this.tokenScopes,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
expiresAt: this.tokenExpiresInDays
|
||||||
|
? Date.now() + this.tokenExpiresInDays * 24 * 60 * 60 * 1000
|
||||||
|
: null,
|
||||||
|
lastUsedAt: null,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
response.tokenValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiTokenManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<ApiToken[]> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ListApiTokens>(
|
||||||
|
'listApiTokens',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return response.tokens.map((t) => new ApiToken(this.clientRef, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(options: {
|
||||||
|
name: string;
|
||||||
|
scopes: interfaces.data.TApiTokenScope[];
|
||||||
|
expiresInDays?: number | null;
|
||||||
|
}): Promise<ApiToken> {
|
||||||
|
return this.build()
|
||||||
|
.setName(options.name)
|
||||||
|
.setScopes(options.scopes)
|
||||||
|
.setExpiresInDays(options.expiresInDays ?? null)
|
||||||
|
.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(): ApiTokenBuilder {
|
||||||
|
return new ApiTokenBuilder(this.clientRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
123
ts_apiclient/classes.certificate.ts
Normal file
123
ts_apiclient/classes.certificate.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class Certificate {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
// Data from ICertificateInfo
|
||||||
|
public domain: string;
|
||||||
|
public routeNames: string[];
|
||||||
|
public status: interfaces.requests.TCertificateStatus;
|
||||||
|
public source: interfaces.requests.TCertificateSource;
|
||||||
|
public tlsMode: 'terminate' | 'terminate-and-reencrypt' | 'passthrough';
|
||||||
|
public expiryDate?: string;
|
||||||
|
public issuer?: string;
|
||||||
|
public issuedAt?: string;
|
||||||
|
public error?: string;
|
||||||
|
public canReprovision: boolean;
|
||||||
|
public backoffInfo?: {
|
||||||
|
failures: number;
|
||||||
|
retryAfter?: string;
|
||||||
|
lastError?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient, data: interfaces.requests.ICertificateInfo) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
this.domain = data.domain;
|
||||||
|
this.routeNames = data.routeNames;
|
||||||
|
this.status = data.status;
|
||||||
|
this.source = data.source;
|
||||||
|
this.tlsMode = data.tlsMode;
|
||||||
|
this.expiryDate = data.expiryDate;
|
||||||
|
this.issuer = data.issuer;
|
||||||
|
this.issuedAt = data.issuedAt;
|
||||||
|
this.error = data.error;
|
||||||
|
this.canReprovision = data.canReprovision;
|
||||||
|
this.backoffInfo = data.backoffInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async reprovision(): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ReprovisionCertificateDomain>(
|
||||||
|
'reprovisionCertificateDomain',
|
||||||
|
this.clientRef.buildRequestPayload({ domain: this.domain }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to reprovision certificate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_DeleteCertificate>(
|
||||||
|
'deleteCertificate',
|
||||||
|
this.clientRef.buildRequestPayload({ domain: this.domain }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to delete certificate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async export(): Promise<{
|
||||||
|
id: string;
|
||||||
|
domainName: string;
|
||||||
|
created: number;
|
||||||
|
validUntil: number;
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
csr: string;
|
||||||
|
} | undefined> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ExportCertificate>(
|
||||||
|
'exportCertificate',
|
||||||
|
this.clientRef.buildRequestPayload({ domain: this.domain }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to export certificate');
|
||||||
|
}
|
||||||
|
return response.cert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICertificateSummary {
|
||||||
|
total: number;
|
||||||
|
valid: number;
|
||||||
|
expiring: number;
|
||||||
|
expired: number;
|
||||||
|
failed: number;
|
||||||
|
unknown: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CertificateManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<{ certificates: Certificate[]; summary: ICertificateSummary }> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetCertificateOverview>(
|
||||||
|
'getCertificateOverview',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
certificates: response.certificates.map((c) => new Certificate(this.clientRef, c)),
|
||||||
|
summary: response.summary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async import(cert: {
|
||||||
|
id: string;
|
||||||
|
domainName: string;
|
||||||
|
created: number;
|
||||||
|
validUntil: number;
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
csr: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ImportCertificate>(
|
||||||
|
'importCertificate',
|
||||||
|
this.clientRef.buildRequestPayload({ cert }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to import certificate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
ts_apiclient/classes.config.ts
Normal file
17
ts_apiclient/classes.config.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class ConfigManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(section?: string): Promise<interfaces.requests.IReq_GetConfiguration['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetConfiguration>(
|
||||||
|
'getConfiguration',
|
||||||
|
this.clientRef.buildRequestPayload({ section }) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
ts_apiclient/classes.dcrouterapiclient.ts
Normal file
112
ts_apiclient/classes.dcrouterapiclient.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
|
||||||
|
import { RouteManager } from './classes.route.js';
|
||||||
|
import { CertificateManager } from './classes.certificate.js';
|
||||||
|
import { ApiTokenManager } from './classes.apitoken.js';
|
||||||
|
import { RemoteIngressManager } from './classes.remoteingress.js';
|
||||||
|
import { StatsManager } from './classes.stats.js';
|
||||||
|
import { ConfigManager } from './classes.config.js';
|
||||||
|
import { LogManager } from './classes.logs.js';
|
||||||
|
import { EmailManager } from './classes.email.js';
|
||||||
|
import { RadiusManager } from './classes.radius.js';
|
||||||
|
|
||||||
|
export interface IDcRouterApiClientOptions {
|
||||||
|
baseUrl: string;
|
||||||
|
apiToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DcRouterApiClient {
|
||||||
|
public baseUrl: string;
|
||||||
|
public apiToken?: string;
|
||||||
|
public identity?: interfaces.data.IIdentity;
|
||||||
|
|
||||||
|
// Resource managers
|
||||||
|
public routes: RouteManager;
|
||||||
|
public certificates: CertificateManager;
|
||||||
|
public apiTokens: ApiTokenManager;
|
||||||
|
public remoteIngress: RemoteIngressManager;
|
||||||
|
public stats: StatsManager;
|
||||||
|
public config: ConfigManager;
|
||||||
|
public logs: LogManager;
|
||||||
|
public emails: EmailManager;
|
||||||
|
public radius: RadiusManager;
|
||||||
|
|
||||||
|
constructor(options: IDcRouterApiClientOptions) {
|
||||||
|
this.baseUrl = options.baseUrl.replace(/\/+$/, '');
|
||||||
|
this.apiToken = options.apiToken;
|
||||||
|
|
||||||
|
this.routes = new RouteManager(this);
|
||||||
|
this.certificates = new CertificateManager(this);
|
||||||
|
this.apiTokens = new ApiTokenManager(this);
|
||||||
|
this.remoteIngress = new RemoteIngressManager(this);
|
||||||
|
this.stats = new StatsManager(this);
|
||||||
|
this.config = new ConfigManager(this);
|
||||||
|
this.logs = new LogManager(this);
|
||||||
|
this.emails = new EmailManager(this);
|
||||||
|
this.radius = new RadiusManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// Auth
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
public async login(username: string, password: string): Promise<interfaces.data.IIdentity> {
|
||||||
|
const response = await this.request<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
|
||||||
|
'adminLoginWithUsernameAndPassword',
|
||||||
|
{ username, password },
|
||||||
|
);
|
||||||
|
if (response.identity) {
|
||||||
|
this.identity = response.identity;
|
||||||
|
}
|
||||||
|
return response.identity!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async logout(): Promise<void> {
|
||||||
|
await this.request<interfaces.requests.IReq_AdminLogout>(
|
||||||
|
'adminLogout',
|
||||||
|
{ identity: this.identity! },
|
||||||
|
);
|
||||||
|
this.identity = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async verifyIdentity(): Promise<{ valid: boolean; identity?: interfaces.data.IIdentity }> {
|
||||||
|
const response = await this.request<interfaces.requests.IReq_VerifyIdentity>(
|
||||||
|
'verifyIdentity',
|
||||||
|
{ identity: this.identity! },
|
||||||
|
);
|
||||||
|
if (response.identity) {
|
||||||
|
this.identity = response.identity;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// Internal request helper
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
public async request<T extends plugins.typedrequestInterfaces.ITypedRequest>(
|
||||||
|
method: string,
|
||||||
|
requestData: T['request'],
|
||||||
|
): Promise<T['response']> {
|
||||||
|
const typedRequest = new plugins.typedrequest.TypedRequest<T>(
|
||||||
|
`${this.baseUrl}/typedrequest`,
|
||||||
|
method,
|
||||||
|
);
|
||||||
|
return typedRequest.fire(requestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a request payload with identity and optional API token auto-injected.
|
||||||
|
*/
|
||||||
|
public buildRequestPayload(extra: Record<string, any> = {}): Record<string, any> {
|
||||||
|
const payload: Record<string, any> = { ...extra };
|
||||||
|
if (this.identity) {
|
||||||
|
payload.identity = this.identity;
|
||||||
|
}
|
||||||
|
if (this.apiToken) {
|
||||||
|
payload.apiToken = this.apiToken;
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
77
ts_apiclient/classes.email.ts
Normal file
77
ts_apiclient/classes.email.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class Email {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
// Data from IEmail
|
||||||
|
public id: string;
|
||||||
|
public direction: interfaces.requests.TEmailDirection;
|
||||||
|
public status: interfaces.requests.TEmailStatus;
|
||||||
|
public from: string;
|
||||||
|
public to: string;
|
||||||
|
public subject: string;
|
||||||
|
public timestamp: string;
|
||||||
|
public messageId: string;
|
||||||
|
public size: string;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient, data: interfaces.requests.IEmail) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
this.id = data.id;
|
||||||
|
this.direction = data.direction;
|
||||||
|
this.status = data.status;
|
||||||
|
this.from = data.from;
|
||||||
|
this.to = data.to;
|
||||||
|
this.subject = data.subject;
|
||||||
|
this.timestamp = data.timestamp;
|
||||||
|
this.messageId = data.messageId;
|
||||||
|
this.size = data.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDetail(): Promise<interfaces.requests.IEmailDetail | null> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetEmailDetail>(
|
||||||
|
'getEmailDetail',
|
||||||
|
this.clientRef.buildRequestPayload({ emailId: this.id }) as any,
|
||||||
|
);
|
||||||
|
return response.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resend(): Promise<{ success: boolean; newQueueId?: string }> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ResendEmail>(
|
||||||
|
'resendEmail',
|
||||||
|
this.clientRef.buildRequestPayload({ emailId: this.id }) as any,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EmailManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<Email[]> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetAllEmails>(
|
||||||
|
'getAllEmails',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return response.emails.map((e) => new Email(this.clientRef, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDetail(emailId: string): Promise<interfaces.requests.IEmailDetail | null> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetEmailDetail>(
|
||||||
|
'getEmailDetail',
|
||||||
|
this.clientRef.buildRequestPayload({ emailId }) as any,
|
||||||
|
);
|
||||||
|
return response.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resend(emailId: string): Promise<{ success: boolean; newQueueId?: string }> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_ResendEmail>(
|
||||||
|
'resendEmail',
|
||||||
|
this.clientRef.buildRequestPayload({ emailId }) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
ts_apiclient/classes.logs.ts
Normal file
37
ts_apiclient/classes.logs.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class LogManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRecent(options?: {
|
||||||
|
level?: 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
search?: string;
|
||||||
|
timeRange?: string;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetRecentLogs['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetRecentLogs>(
|
||||||
|
'getRecentLogs',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStream(options?: {
|
||||||
|
follow?: boolean;
|
||||||
|
filters?: {
|
||||||
|
level?: string[];
|
||||||
|
category?: string[];
|
||||||
|
};
|
||||||
|
}): Promise<interfaces.requests.IReq_GetLogStream['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetLogStream>(
|
||||||
|
'getLogStream',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
180
ts_apiclient/classes.radius.ts
Normal file
180
ts_apiclient/classes.radius.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// Sub-managers
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
export class RadiusClientManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<Array<{
|
||||||
|
name: string;
|
||||||
|
ipRange: string;
|
||||||
|
description?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}>> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetRadiusClients>(
|
||||||
|
'getRadiusClients',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return response.clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async set(client: {
|
||||||
|
name: string;
|
||||||
|
ipRange: string;
|
||||||
|
secret: string;
|
||||||
|
description?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_SetRadiusClient>(
|
||||||
|
'setRadiusClient',
|
||||||
|
this.clientRef.buildRequestPayload({ client }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to set RADIUS client');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async remove(name: string): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_RemoveRadiusClient>(
|
||||||
|
'removeRadiusClient',
|
||||||
|
this.clientRef.buildRequestPayload({ name }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to remove RADIUS client');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RadiusVlanManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<interfaces.requests.IReq_GetVlanMappings['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetVlanMappings>(
|
||||||
|
'getVlanMappings',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async set(mapping: {
|
||||||
|
mac: string;
|
||||||
|
vlan: number;
|
||||||
|
description?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_SetVlanMapping>(
|
||||||
|
'setVlanMapping',
|
||||||
|
this.clientRef.buildRequestPayload({ mapping }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to set VLAN mapping');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async remove(mac: string): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_RemoveVlanMapping>(
|
||||||
|
'removeVlanMapping',
|
||||||
|
this.clientRef.buildRequestPayload({ mac }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to remove VLAN mapping');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateConfig(options: {
|
||||||
|
defaultVlan?: number;
|
||||||
|
allowUnknownMacs?: boolean;
|
||||||
|
}): Promise<{ defaultVlan: number; allowUnknownMacs: boolean }> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_UpdateVlanConfig>(
|
||||||
|
'updateVlanConfig',
|
||||||
|
this.clientRef.buildRequestPayload(options) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error('Failed to update VLAN config');
|
||||||
|
}
|
||||||
|
return response.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testAssignment(mac: string): Promise<interfaces.requests.IReq_TestVlanAssignment['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_TestVlanAssignment>(
|
||||||
|
'testVlanAssignment',
|
||||||
|
this.clientRef.buildRequestPayload({ mac }) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RadiusSessionManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(filter?: {
|
||||||
|
username?: string;
|
||||||
|
nasIpAddress?: string;
|
||||||
|
vlanId?: number;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetRadiusSessions['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetRadiusSessions>(
|
||||||
|
'getRadiusSessions',
|
||||||
|
this.clientRef.buildRequestPayload({ filter }) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disconnect(sessionId: string, reason?: string): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_DisconnectRadiusSession>(
|
||||||
|
'disconnectRadiusSession',
|
||||||
|
this.clientRef.buildRequestPayload({ sessionId, reason }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to disconnect session');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// Main RADIUS Manager
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
export class RadiusManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
public clients: RadiusClientManager;
|
||||||
|
public vlans: RadiusVlanManager;
|
||||||
|
public sessions: RadiusSessionManager;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
this.clients = new RadiusClientManager(clientRef);
|
||||||
|
this.vlans = new RadiusVlanManager(clientRef);
|
||||||
|
this.sessions = new RadiusSessionManager(clientRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAccountingSummary(
|
||||||
|
startTime: number,
|
||||||
|
endTime: number,
|
||||||
|
): Promise<interfaces.requests.IReq_GetRadiusAccountingSummary['response']['summary']> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetRadiusAccountingSummary>(
|
||||||
|
'getRadiusAccountingSummary',
|
||||||
|
this.clientRef.buildRequestPayload({ startTime, endTime }) as any,
|
||||||
|
);
|
||||||
|
return response.summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStatistics(): Promise<interfaces.requests.IReq_GetRadiusStatistics['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetRadiusStatistics>(
|
||||||
|
'getRadiusStatistics',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
185
ts_apiclient/classes.remoteingress.ts
Normal file
185
ts_apiclient/classes.remoteingress.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class RemoteIngress {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
// Data from IRemoteIngress
|
||||||
|
public id: string;
|
||||||
|
public name: string;
|
||||||
|
public secret: string;
|
||||||
|
public listenPorts: number[];
|
||||||
|
public enabled: boolean;
|
||||||
|
public autoDerivePorts: boolean;
|
||||||
|
public tags?: string[];
|
||||||
|
public createdAt: number;
|
||||||
|
public updatedAt: number;
|
||||||
|
public effectiveListenPorts?: number[];
|
||||||
|
public manualPorts?: number[];
|
||||||
|
public derivedPorts?: number[];
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient, data: interfaces.data.IRemoteIngress) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
this.id = data.id;
|
||||||
|
this.name = data.name;
|
||||||
|
this.secret = data.secret;
|
||||||
|
this.listenPorts = data.listenPorts;
|
||||||
|
this.enabled = data.enabled;
|
||||||
|
this.autoDerivePorts = data.autoDerivePorts;
|
||||||
|
this.tags = data.tags;
|
||||||
|
this.createdAt = data.createdAt;
|
||||||
|
this.updatedAt = data.updatedAt;
|
||||||
|
this.effectiveListenPorts = data.effectiveListenPorts;
|
||||||
|
this.manualPorts = data.manualPorts;
|
||||||
|
this.derivedPorts = data.derivedPorts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(changes: {
|
||||||
|
name?: string;
|
||||||
|
listenPorts?: number[];
|
||||||
|
autoDerivePorts?: boolean;
|
||||||
|
enabled?: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
}): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_UpdateRemoteIngress>(
|
||||||
|
'updateRemoteIngress',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.id, ...changes }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error('Failed to update remote ingress');
|
||||||
|
}
|
||||||
|
// Update local state from response
|
||||||
|
const edge = response.edge;
|
||||||
|
this.name = edge.name;
|
||||||
|
this.listenPorts = edge.listenPorts;
|
||||||
|
this.enabled = edge.enabled;
|
||||||
|
this.autoDerivePorts = edge.autoDerivePorts;
|
||||||
|
this.tags = edge.tags;
|
||||||
|
this.updatedAt = edge.updatedAt;
|
||||||
|
this.effectiveListenPorts = edge.effectiveListenPorts;
|
||||||
|
this.manualPorts = edge.manualPorts;
|
||||||
|
this.derivedPorts = edge.derivedPorts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_DeleteRemoteIngress>(
|
||||||
|
'deleteRemoteIngress',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.id }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to delete remote ingress');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async regenerateSecret(): Promise<string> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
|
||||||
|
'regenerateRemoteIngressSecret',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.id }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error('Failed to regenerate secret');
|
||||||
|
}
|
||||||
|
this.secret = response.secret;
|
||||||
|
return response.secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getConnectionToken(hubHost?: string): Promise<string | undefined> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetRemoteIngressConnectionToken>(
|
||||||
|
'getRemoteIngressConnectionToken',
|
||||||
|
this.clientRef.buildRequestPayload({ edgeId: this.id, hubHost }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to get connection token');
|
||||||
|
}
|
||||||
|
return response.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RemoteIngressBuilder {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
private edgeName: string = '';
|
||||||
|
private edgeListenPorts?: number[];
|
||||||
|
private edgeAutoDerivePorts?: boolean;
|
||||||
|
private edgeTags?: string[];
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setName(name: string): this {
|
||||||
|
this.edgeName = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setListenPorts(ports: number[]): this {
|
||||||
|
this.edgeListenPorts = ports;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAutoDerivePorts(auto: boolean): this {
|
||||||
|
this.edgeAutoDerivePorts = auto;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTags(tags: string[]): this {
|
||||||
|
this.edgeTags = tags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(): Promise<RemoteIngress> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_CreateRemoteIngress>(
|
||||||
|
'createRemoteIngress',
|
||||||
|
this.clientRef.buildRequestPayload({
|
||||||
|
name: this.edgeName,
|
||||||
|
listenPorts: this.edgeListenPorts,
|
||||||
|
autoDerivePorts: this.edgeAutoDerivePorts,
|
||||||
|
tags: this.edgeTags,
|
||||||
|
}) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error('Failed to create remote ingress');
|
||||||
|
}
|
||||||
|
return new RemoteIngress(this.clientRef, response.edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RemoteIngressManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<RemoteIngress[]> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetRemoteIngresses>(
|
||||||
|
'getRemoteIngresses',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return response.edges.map((e) => new RemoteIngress(this.clientRef, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStatuses(): Promise<interfaces.data.IRemoteIngressStatus[]> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetRemoteIngressStatus>(
|
||||||
|
'getRemoteIngressStatus',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return response.statuses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(options: {
|
||||||
|
name: string;
|
||||||
|
listenPorts?: number[];
|
||||||
|
autoDerivePorts?: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
}): Promise<RemoteIngress> {
|
||||||
|
const builder = this.build().setName(options.name);
|
||||||
|
if (options.listenPorts) builder.setListenPorts(options.listenPorts);
|
||||||
|
if (options.autoDerivePorts !== undefined) builder.setAutoDerivePorts(options.autoDerivePorts);
|
||||||
|
if (options.tags) builder.setTags(options.tags);
|
||||||
|
return builder.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(): RemoteIngressBuilder {
|
||||||
|
return new RemoteIngressBuilder(this.clientRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
203
ts_apiclient/classes.route.ts
Normal file
203
ts_apiclient/classes.route.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { IRouteConfig } from '@push.rocks/smartproxy';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
export class Route {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
// Data from IMergedRoute
|
||||||
|
public routeConfig: IRouteConfig;
|
||||||
|
public source: 'hardcoded' | 'programmatic';
|
||||||
|
public enabled: boolean;
|
||||||
|
public overridden: boolean;
|
||||||
|
public storedRouteId?: string;
|
||||||
|
public createdAt?: number;
|
||||||
|
public updatedAt?: number;
|
||||||
|
|
||||||
|
// Convenience accessors
|
||||||
|
public get name(): string {
|
||||||
|
return this.routeConfig.name || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient, data: interfaces.data.IMergedRoute) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
this.routeConfig = data.route;
|
||||||
|
this.source = data.source;
|
||||||
|
this.enabled = data.enabled;
|
||||||
|
this.overridden = data.overridden;
|
||||||
|
this.storedRouteId = data.storedRouteId;
|
||||||
|
this.createdAt = data.createdAt;
|
||||||
|
this.updatedAt = data.updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(changes: Partial<IRouteConfig>): Promise<void> {
|
||||||
|
if (!this.storedRouteId) {
|
||||||
|
throw new Error('Cannot update a hardcoded route. Use setOverride() instead.');
|
||||||
|
}
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_UpdateRoute>(
|
||||||
|
'updateRoute',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.storedRouteId, route: changes }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to update route');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(): Promise<void> {
|
||||||
|
if (!this.storedRouteId) {
|
||||||
|
throw new Error('Cannot delete a hardcoded route. Use setOverride() instead.');
|
||||||
|
}
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_DeleteRoute>(
|
||||||
|
'deleteRoute',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.storedRouteId }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to delete route');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toggle(enabled: boolean): Promise<void> {
|
||||||
|
if (!this.storedRouteId) {
|
||||||
|
throw new Error('Cannot toggle a hardcoded route. Use setOverride() instead.');
|
||||||
|
}
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_ToggleRoute>(
|
||||||
|
'toggleRoute',
|
||||||
|
this.clientRef.buildRequestPayload({ id: this.storedRouteId, enabled }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to toggle route');
|
||||||
|
}
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setOverride(enabled: boolean): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_SetRouteOverride>(
|
||||||
|
'setRouteOverride',
|
||||||
|
this.clientRef.buildRequestPayload({ routeName: this.name, enabled }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to set route override');
|
||||||
|
}
|
||||||
|
this.overridden = true;
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeOverride(): Promise<void> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_RemoveRouteOverride>(
|
||||||
|
'removeRouteOverride',
|
||||||
|
this.clientRef.buildRequestPayload({ routeName: this.name }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to remove route override');
|
||||||
|
}
|
||||||
|
this.overridden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RouteBuilder {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
private routeConfig: Partial<IRouteConfig> = {};
|
||||||
|
private isEnabled: boolean = true;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setName(name: string): this {
|
||||||
|
this.routeConfig.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setMatch(match: IRouteConfig['match']): this {
|
||||||
|
this.routeConfig.match = match;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAction(action: IRouteConfig['action']): this {
|
||||||
|
this.routeConfig.action = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTls(tls: IRouteConfig['action']['tls']): this {
|
||||||
|
if (!this.routeConfig.action) {
|
||||||
|
this.routeConfig.action = { type: 'forward' } as IRouteConfig['action'];
|
||||||
|
}
|
||||||
|
this.routeConfig.action!.tls = tls;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setEnabled(enabled: boolean): this {
|
||||||
|
this.isEnabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async save(): Promise<Route> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_CreateRoute>(
|
||||||
|
'createRoute',
|
||||||
|
this.clientRef.buildRequestPayload({
|
||||||
|
route: this.routeConfig as IRouteConfig,
|
||||||
|
enabled: this.isEnabled,
|
||||||
|
}) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to create route');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a Route instance by re-fetching the list
|
||||||
|
// The created route is programmatic, so we find it by storedRouteId
|
||||||
|
const { routes } = await new RouteManager(this.clientRef).list();
|
||||||
|
const created = routes.find((r) => r.storedRouteId === response.storedRouteId);
|
||||||
|
if (created) {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: construct from known data
|
||||||
|
return new Route(this.clientRef, {
|
||||||
|
route: this.routeConfig as IRouteConfig,
|
||||||
|
source: 'programmatic',
|
||||||
|
enabled: this.isEnabled,
|
||||||
|
overridden: false,
|
||||||
|
storedRouteId: response.storedRouteId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RouteManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async list(): Promise<{ routes: Route[]; warnings: interfaces.data.IRouteWarning[] }> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetMergedRoutes>(
|
||||||
|
'getMergedRoutes',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
routes: response.routes.map((r) => new Route(this.clientRef, r)),
|
||||||
|
warnings: response.warnings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(routeConfig: IRouteConfig, enabled?: boolean): Promise<Route> {
|
||||||
|
const response = await this.clientRef.request<interfaces.requests.IReq_CreateRoute>(
|
||||||
|
'createRoute',
|
||||||
|
this.clientRef.buildRequestPayload({ route: routeConfig, enabled: enabled ?? true }) as any,
|
||||||
|
);
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.message || 'Failed to create route');
|
||||||
|
}
|
||||||
|
return new Route(this.clientRef, {
|
||||||
|
route: routeConfig,
|
||||||
|
source: 'programmatic',
|
||||||
|
enabled: enabled ?? true,
|
||||||
|
overridden: false,
|
||||||
|
storedRouteId: response.storedRouteId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(): RouteBuilder {
|
||||||
|
return new RouteBuilder(this.clientRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
ts_apiclient/classes.stats.ts
Normal file
111
ts_apiclient/classes.stats.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import * as interfaces from '../ts_interfaces/index.js';
|
||||||
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
type TTimeRange = '1h' | '6h' | '24h' | '7d' | '30d';
|
||||||
|
|
||||||
|
export class StatsManager {
|
||||||
|
private clientRef: DcRouterApiClient;
|
||||||
|
|
||||||
|
constructor(clientRef: DcRouterApiClient) {
|
||||||
|
this.clientRef = clientRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getServer(options?: {
|
||||||
|
timeRange?: TTimeRange;
|
||||||
|
includeHistory?: boolean;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetServerStatistics['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetServerStatistics>(
|
||||||
|
'getServerStatistics',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getEmail(options?: {
|
||||||
|
timeRange?: TTimeRange;
|
||||||
|
domain?: string;
|
||||||
|
includeDetails?: boolean;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetEmailStatistics['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetEmailStatistics>(
|
||||||
|
'getEmailStatistics',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDns(options?: {
|
||||||
|
timeRange?: TTimeRange;
|
||||||
|
domain?: string;
|
||||||
|
includeQueryTypes?: boolean;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetDnsStatistics['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetDnsStatistics>(
|
||||||
|
'getDnsStatistics',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRateLimits(options?: {
|
||||||
|
domain?: string;
|
||||||
|
ip?: string;
|
||||||
|
includeBlocked?: boolean;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetRateLimitStatus['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetRateLimitStatus>(
|
||||||
|
'getRateLimitStatus',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSecurity(options?: {
|
||||||
|
timeRange?: TTimeRange;
|
||||||
|
includeDetails?: boolean;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetSecurityMetrics['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetSecurityMetrics>(
|
||||||
|
'getSecurityMetrics',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getConnections(options?: {
|
||||||
|
protocol?: 'smtp' | 'smtps' | 'http' | 'https';
|
||||||
|
state?: string;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetActiveConnections['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetActiveConnections>(
|
||||||
|
'getActiveConnections',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getQueues(options?: {
|
||||||
|
queueName?: string;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetQueueStatus['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetQueueStatus>(
|
||||||
|
'getQueueStatus',
|
||||||
|
this.clientRef.buildRequestPayload(options || {}) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getHealth(detailed?: boolean): Promise<interfaces.requests.IReq_GetHealthStatus['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetHealthStatus>(
|
||||||
|
'getHealthStatus',
|
||||||
|
this.clientRef.buildRequestPayload({ detailed }) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getNetwork(): Promise<interfaces.requests.IReq_GetNetworkStats['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetNetworkStats>(
|
||||||
|
'getNetworkStats',
|
||||||
|
this.clientRef.buildRequestPayload() as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCombined(sections?: {
|
||||||
|
server?: boolean;
|
||||||
|
email?: boolean;
|
||||||
|
dns?: boolean;
|
||||||
|
security?: boolean;
|
||||||
|
network?: boolean;
|
||||||
|
}): Promise<interfaces.requests.IReq_GetCombinedMetrics['response']> {
|
||||||
|
return this.clientRef.request<interfaces.requests.IReq_GetCombinedMetrics>(
|
||||||
|
'getCombinedMetrics',
|
||||||
|
this.clientRef.buildRequestPayload({ sections }) as any,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
ts_apiclient/index.ts
Normal file
15
ts_apiclient/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Main client
|
||||||
|
export { DcRouterApiClient, type IDcRouterApiClientOptions } from './classes.dcrouterapiclient.js';
|
||||||
|
|
||||||
|
// Resource classes
|
||||||
|
export { Route, RouteBuilder, RouteManager } from './classes.route.js';
|
||||||
|
export { Certificate, CertificateManager, type ICertificateSummary } from './classes.certificate.js';
|
||||||
|
export { ApiToken, ApiTokenBuilder, ApiTokenManager } from './classes.apitoken.js';
|
||||||
|
export { RemoteIngress, RemoteIngressBuilder, RemoteIngressManager } from './classes.remoteingress.js';
|
||||||
|
export { Email, EmailManager } from './classes.email.js';
|
||||||
|
|
||||||
|
// Read-only managers
|
||||||
|
export { StatsManager } from './classes.stats.js';
|
||||||
|
export { ConfigManager } from './classes.config.js';
|
||||||
|
export { LogManager } from './classes.logs.js';
|
||||||
|
export { RadiusManager, RadiusClientManager, RadiusVlanManager, RadiusSessionManager } from './classes.radius.js';
|
||||||
8
ts_apiclient/plugins.ts
Normal file
8
ts_apiclient/plugins.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// @api.global scope
|
||||||
|
import * as typedrequest from '@api.global/typedrequest';
|
||||||
|
import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces';
|
||||||
|
|
||||||
|
export {
|
||||||
|
typedrequest,
|
||||||
|
typedrequestInterfaces,
|
||||||
|
};
|
||||||
@@ -16,7 +16,7 @@ export interface IReq_CreateApiToken extends plugins.typedrequestInterfaces.impl
|
|||||||
> {
|
> {
|
||||||
method: 'createApiToken';
|
method: 'createApiToken';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
name: string;
|
name: string;
|
||||||
scopes: TApiTokenScope[];
|
scopes: TApiTokenScope[];
|
||||||
expiresInDays?: number | null;
|
expiresInDays?: number | null;
|
||||||
@@ -38,7 +38,7 @@ export interface IReq_ListApiTokens extends plugins.typedrequestInterfaces.imple
|
|||||||
> {
|
> {
|
||||||
method: 'listApiTokens';
|
method: 'listApiTokens';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
tokens: IApiTokenInfo[];
|
tokens: IApiTokenInfo[];
|
||||||
@@ -54,7 +54,7 @@ export interface IReq_RevokeApiToken extends plugins.typedrequestInterfaces.impl
|
|||||||
> {
|
> {
|
||||||
method: 'revokeApiToken';
|
method: 'revokeApiToken';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -73,7 +73,7 @@ export interface IReq_RollApiToken extends plugins.typedrequestInterfaces.implem
|
|||||||
> {
|
> {
|
||||||
method: 'rollApiToken';
|
method: 'rollApiToken';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -92,7 +92,7 @@ export interface IReq_ToggleApiToken extends plugins.typedrequestInterfaces.impl
|
|||||||
> {
|
> {
|
||||||
method: 'toggleApiToken';
|
method: 'toggleApiToken';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
id: string;
|
id: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export interface IReq_GetCertificateOverview extends plugins.typedrequestInterfa
|
|||||||
> {
|
> {
|
||||||
method: 'getCertificateOverview';
|
method: 'getCertificateOverview';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
certificates: ICertificateInfo[];
|
certificates: ICertificateInfo[];
|
||||||
@@ -50,7 +50,7 @@ export interface IReq_ReprovisionCertificate extends plugins.typedrequestInterfa
|
|||||||
> {
|
> {
|
||||||
method: 'reprovisionCertificate';
|
method: 'reprovisionCertificate';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
routeName: string;
|
routeName: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -66,7 +66,7 @@ export interface IReq_ReprovisionCertificateDomain extends plugins.typedrequestI
|
|||||||
> {
|
> {
|
||||||
method: 'reprovisionCertificateDomain';
|
method: 'reprovisionCertificateDomain';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
domain: string;
|
domain: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -82,7 +82,7 @@ export interface IReq_DeleteCertificate extends plugins.typedrequestInterfaces.i
|
|||||||
> {
|
> {
|
||||||
method: 'deleteCertificate';
|
method: 'deleteCertificate';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
domain: string;
|
domain: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -98,7 +98,7 @@ export interface IReq_ExportCertificate extends plugins.typedrequestInterfaces.i
|
|||||||
> {
|
> {
|
||||||
method: 'exportCertificate';
|
method: 'exportCertificate';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
domain: string;
|
domain: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -123,7 +123,7 @@ export interface IReq_ImportCertificate extends plugins.typedrequestInterfaces.i
|
|||||||
> {
|
> {
|
||||||
method: 'importCertificate';
|
method: 'importCertificate';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
cert: {
|
cert: {
|
||||||
id: string;
|
id: string;
|
||||||
domainName: string;
|
domainName: string;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export interface IReq_GetConfiguration extends plugins.typedrequestInterfaces.im
|
|||||||
> {
|
> {
|
||||||
method: 'getConfiguration';
|
method: 'getConfiguration';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
section?: string;
|
section?: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export interface IReq_GetAllEmails extends plugins.typedrequestInterfaces.implem
|
|||||||
> {
|
> {
|
||||||
method: 'getAllEmails';
|
method: 'getAllEmails';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
emails: IEmail[];
|
emails: IEmail[];
|
||||||
@@ -84,7 +84,7 @@ export interface IReq_GetEmailDetail extends plugins.typedrequestInterfaces.impl
|
|||||||
> {
|
> {
|
||||||
method: 'getEmailDetail';
|
method: 'getEmailDetail';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
emailId: string;
|
emailId: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -101,7 +101,7 @@ export interface IReq_ResendEmail extends plugins.typedrequestInterfaces.impleme
|
|||||||
> {
|
> {
|
||||||
method: 'resendEmail';
|
method: 'resendEmail';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
emailId: string;
|
emailId: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface IReq_GetRecentLogs extends plugins.typedrequestInterfaces.imple
|
|||||||
> {
|
> {
|
||||||
method: 'getRecentLogs';
|
method: 'getRecentLogs';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
level?: 'debug' | 'info' | 'warn' | 'error';
|
level?: 'debug' | 'info' | 'warn' | 'error';
|
||||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@@ -31,7 +31,7 @@ export interface IReq_GetLogStream extends plugins.typedrequestInterfaces.implem
|
|||||||
> {
|
> {
|
||||||
method: 'getLogStream';
|
method: 'getLogStream';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
follow?: boolean;
|
follow?: boolean;
|
||||||
filters?: {
|
filters?: {
|
||||||
level?: string[];
|
level?: string[];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface IReq_GetRadiusClients extends plugins.typedrequestInterfaces.im
|
|||||||
> {
|
> {
|
||||||
method: 'getRadiusClients';
|
method: 'getRadiusClients';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
clients: Array<{
|
clients: Array<{
|
||||||
@@ -35,7 +35,7 @@ export interface IReq_SetRadiusClient extends plugins.typedrequestInterfaces.imp
|
|||||||
> {
|
> {
|
||||||
method: 'setRadiusClient';
|
method: 'setRadiusClient';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
client: {
|
client: {
|
||||||
name: string;
|
name: string;
|
||||||
ipRange: string;
|
ipRange: string;
|
||||||
@@ -59,7 +59,7 @@ export interface IReq_RemoveRadiusClient extends plugins.typedrequestInterfaces.
|
|||||||
> {
|
> {
|
||||||
method: 'removeRadiusClient';
|
method: 'removeRadiusClient';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -81,7 +81,7 @@ export interface IReq_GetVlanMappings extends plugins.typedrequestInterfaces.imp
|
|||||||
> {
|
> {
|
||||||
method: 'getVlanMappings';
|
method: 'getVlanMappings';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
mappings: Array<{
|
mappings: Array<{
|
||||||
@@ -108,7 +108,7 @@ export interface IReq_SetVlanMapping extends plugins.typedrequestInterfaces.impl
|
|||||||
> {
|
> {
|
||||||
method: 'setVlanMapping';
|
method: 'setVlanMapping';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
mapping: {
|
mapping: {
|
||||||
mac: string;
|
mac: string;
|
||||||
vlan: number;
|
vlan: number;
|
||||||
@@ -139,7 +139,7 @@ export interface IReq_RemoveVlanMapping extends plugins.typedrequestInterfaces.i
|
|||||||
> {
|
> {
|
||||||
method: 'removeVlanMapping';
|
method: 'removeVlanMapping';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
mac: string;
|
mac: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -157,7 +157,7 @@ export interface IReq_UpdateVlanConfig extends plugins.typedrequestInterfaces.im
|
|||||||
> {
|
> {
|
||||||
method: 'updateVlanConfig';
|
method: 'updateVlanConfig';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
defaultVlan?: number;
|
defaultVlan?: number;
|
||||||
allowUnknownMacs?: boolean;
|
allowUnknownMacs?: boolean;
|
||||||
};
|
};
|
||||||
@@ -179,7 +179,7 @@ export interface IReq_TestVlanAssignment extends plugins.typedrequestInterfaces.
|
|||||||
> {
|
> {
|
||||||
method: 'testVlanAssignment';
|
method: 'testVlanAssignment';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
mac: string;
|
mac: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -207,7 +207,7 @@ export interface IReq_GetRadiusSessions extends plugins.typedrequestInterfaces.i
|
|||||||
> {
|
> {
|
||||||
method: 'getRadiusSessions';
|
method: 'getRadiusSessions';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
filter?: {
|
filter?: {
|
||||||
username?: string;
|
username?: string;
|
||||||
nasIpAddress?: string;
|
nasIpAddress?: string;
|
||||||
@@ -243,7 +243,7 @@ export interface IReq_DisconnectRadiusSession extends plugins.typedrequestInterf
|
|||||||
> {
|
> {
|
||||||
method: 'disconnectRadiusSession';
|
method: 'disconnectRadiusSession';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
};
|
};
|
||||||
@@ -262,7 +262,7 @@ export interface IReq_GetRadiusAccountingSummary extends plugins.typedrequestInt
|
|||||||
> {
|
> {
|
||||||
method: 'getRadiusAccountingSummary';
|
method: 'getRadiusAccountingSummary';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime: number;
|
endTime: number;
|
||||||
};
|
};
|
||||||
@@ -296,7 +296,7 @@ export interface IReq_GetRadiusStatistics extends plugins.typedrequestInterfaces
|
|||||||
> {
|
> {
|
||||||
method: 'getRadiusStatistics';
|
method: 'getRadiusStatistics';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
stats: {
|
stats: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces
|
|||||||
> {
|
> {
|
||||||
method: 'createRemoteIngress';
|
method: 'createRemoteIngress';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
name: string;
|
name: string;
|
||||||
listenPorts?: number[];
|
listenPorts?: number[];
|
||||||
autoDerivePorts?: boolean;
|
autoDerivePorts?: boolean;
|
||||||
@@ -36,7 +36,7 @@ export interface IReq_DeleteRemoteIngress extends plugins.typedrequestInterfaces
|
|||||||
> {
|
> {
|
||||||
method: 'deleteRemoteIngress';
|
method: 'deleteRemoteIngress';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -54,7 +54,7 @@ export interface IReq_UpdateRemoteIngress extends plugins.typedrequestInterfaces
|
|||||||
> {
|
> {
|
||||||
method: 'updateRemoteIngress';
|
method: 'updateRemoteIngress';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
listenPorts?: number[];
|
listenPorts?: number[];
|
||||||
@@ -77,7 +77,7 @@ export interface IReq_RegenerateRemoteIngressSecret extends plugins.typedrequest
|
|||||||
> {
|
> {
|
||||||
method: 'regenerateRemoteIngressSecret';
|
method: 'regenerateRemoteIngressSecret';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -95,7 +95,7 @@ export interface IReq_GetRemoteIngresses extends plugins.typedrequestInterfaces.
|
|||||||
> {
|
> {
|
||||||
method: 'getRemoteIngresses';
|
method: 'getRemoteIngresses';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
edges: IRemoteIngress[];
|
edges: IRemoteIngress[];
|
||||||
@@ -111,7 +111,7 @@ export interface IReq_GetRemoteIngressStatus extends plugins.typedrequestInterfa
|
|||||||
> {
|
> {
|
||||||
method: 'getRemoteIngressStatus';
|
method: 'getRemoteIngressStatus';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
statuses: IRemoteIngressStatus[];
|
statuses: IRemoteIngressStatus[];
|
||||||
@@ -128,7 +128,7 @@ export interface IReq_GetRemoteIngressConnectionToken extends plugins.typedreque
|
|||||||
> {
|
> {
|
||||||
method: 'getRemoteIngressConnectionToken';
|
method: 'getRemoteIngressConnectionToken';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
edgeId: string;
|
edgeId: string;
|
||||||
hubHost?: string;
|
hubHost?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface IReq_GetServerStatistics extends plugins.typedrequestInterfaces
|
|||||||
> {
|
> {
|
||||||
method: 'getServerStatistics';
|
method: 'getServerStatistics';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
includeHistory?: boolean;
|
includeHistory?: boolean;
|
||||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||||
};
|
};
|
||||||
@@ -29,7 +29,7 @@ export interface IReq_GetEmailStatistics extends plugins.typedrequestInterfaces.
|
|||||||
> {
|
> {
|
||||||
method: 'getEmailStatistics';
|
method: 'getEmailStatistics';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||||
domain?: string;
|
domain?: string;
|
||||||
includeDetails?: boolean;
|
includeDetails?: boolean;
|
||||||
@@ -49,7 +49,7 @@ export interface IReq_GetDnsStatistics extends plugins.typedrequestInterfaces.im
|
|||||||
> {
|
> {
|
||||||
method: 'getDnsStatistics';
|
method: 'getDnsStatistics';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||||
domain?: string;
|
domain?: string;
|
||||||
includeQueryTypes?: boolean;
|
includeQueryTypes?: boolean;
|
||||||
@@ -69,7 +69,7 @@ export interface IReq_GetRateLimitStatus extends plugins.typedrequestInterfaces.
|
|||||||
> {
|
> {
|
||||||
method: 'getRateLimitStatus';
|
method: 'getRateLimitStatus';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
includeBlocked?: boolean;
|
includeBlocked?: boolean;
|
||||||
@@ -91,7 +91,7 @@ export interface IReq_GetSecurityMetrics extends plugins.typedrequestInterfaces.
|
|||||||
> {
|
> {
|
||||||
method: 'getSecurityMetrics';
|
method: 'getSecurityMetrics';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||||
includeDetails?: boolean;
|
includeDetails?: boolean;
|
||||||
};
|
};
|
||||||
@@ -112,7 +112,7 @@ export interface IReq_GetActiveConnections extends plugins.typedrequestInterface
|
|||||||
> {
|
> {
|
||||||
method: 'getActiveConnections';
|
method: 'getActiveConnections';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
protocol?: 'smtp' | 'smtps' | 'http' | 'https';
|
protocol?: 'smtp' | 'smtps' | 'http' | 'https';
|
||||||
state?: string;
|
state?: string;
|
||||||
};
|
};
|
||||||
@@ -137,7 +137,7 @@ export interface IReq_GetQueueStatus extends plugins.typedrequestInterfaces.impl
|
|||||||
> {
|
> {
|
||||||
method: 'getQueueStatus';
|
method: 'getQueueStatus';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
queueName?: string;
|
queueName?: string;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
@@ -153,10 +153,31 @@ export interface IReq_GetHealthStatus extends plugins.typedrequestInterfaces.imp
|
|||||||
> {
|
> {
|
||||||
method: 'getHealthStatus';
|
method: 'getHealthStatus';
|
||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity: authInterfaces.IIdentity;
|
||||||
detailed?: boolean;
|
detailed?: boolean;
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
health: statsInterfaces.IHealthStatus;
|
health: statsInterfaces.IHealthStatus;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network Stats (raw SmartProxy network data)
|
||||||
|
export interface IReq_GetNetworkStats extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetNetworkStats
|
||||||
|
> {
|
||||||
|
method: 'getNetworkStats';
|
||||||
|
request: {
|
||||||
|
identity: authInterfaces.IIdentity;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
connectionsByIP: Array<{ ip: string; count: number }>;
|
||||||
|
throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
|
||||||
|
topIPs: Array<{ ip: string; count: number }>;
|
||||||
|
totalDataTransferred: { bytesIn: number; bytesOut: number };
|
||||||
|
throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
|
||||||
|
throughputByIP: Array<{ ip: string; in: number; out: number }>;
|
||||||
|
requestsPerSecond: number;
|
||||||
|
requestsTotal: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '10.1.9',
|
version: '11.1.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,9 +238,12 @@ interface IActionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getActionContext = (): IActionContext => {
|
const getActionContext = (): IActionContext => {
|
||||||
return {
|
const identity = loginStatePart.getState().identity;
|
||||||
identity: loginStatePart.getState().identity,
|
// Treat expired JWTs as no identity — prevents stale persisted sessions from firing requests
|
||||||
};
|
if (identity && identity.expiresAt && identity.expiresAt < Date.now()) {
|
||||||
|
return { identity: null };
|
||||||
|
}
|
||||||
|
return { identity };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Login Action
|
// Login Action
|
||||||
@@ -271,24 +274,23 @@ export const loginAction = loginStatePart.createAction<{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Logout Action
|
// Logout Action — always clears state, even if identity is expired/missing
|
||||||
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
|
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
if (!context.identity) return statePartArg.getState();
|
|
||||||
|
|
||||||
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
// Try to notify server, but don't block logout if identity is missing/expired
|
||||||
interfaces.requests.IReq_AdminLogout
|
if (context.identity) {
|
||||||
>('/typedrequest', 'adminLogout');
|
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_AdminLogout
|
||||||
try {
|
>('/typedrequest', 'adminLogout');
|
||||||
await typedRequest.fire({
|
try {
|
||||||
identity: context.identity,
|
await typedRequest.fire({ identity: context.identity });
|
||||||
});
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error('Logout error:', error);
|
||||||
console.error('Logout error:', error);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear login state regardless
|
// Always clear login state
|
||||||
return {
|
return {
|
||||||
identity: null,
|
identity: null,
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
@@ -298,8 +300,8 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
|
|||||||
// Fetch All Stats Action - Using combined endpoint for efficiency
|
// Fetch All Stats Action - Using combined endpoint for efficiency
|
||||||
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => {
|
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use combined metrics endpoint - single request instead of 4
|
// Use combined metrics endpoint - single request instead of 4
|
||||||
@@ -340,8 +342,8 @@ export const fetchAllStatsAction = statsStatePart.createAction(async (statePartA
|
|||||||
// Fetch Configuration Action (read-only)
|
// Fetch Configuration Action (read-only)
|
||||||
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => {
|
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -373,6 +375,7 @@ export const fetchRecentLogsAction = logStatePart.createAction<{
|
|||||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||||
}>(async (statePartArg, dataArg) => {
|
}>(async (statePartArg, dataArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
if (!context.identity) return statePartArg.getState();
|
||||||
|
|
||||||
const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
interfaces.requests.IReq_GetRecentLogs
|
interfaces.requests.IReq_GetRecentLogs
|
||||||
@@ -448,8 +451,8 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
|||||||
// Fetch Network Stats Action
|
// Fetch Network Stats Action
|
||||||
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => {
|
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch active connections using the existing endpoint
|
// Fetch active connections using the existing endpoint
|
||||||
@@ -522,6 +525,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|||||||
export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg) => {
|
export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -554,6 +558,7 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
|
|||||||
export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg) => {
|
export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -697,6 +702,7 @@ export async function fetchConnectionToken(edgeId: string) {
|
|||||||
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
|
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -903,6 +909,7 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
|
|||||||
export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => {
|
export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -1068,6 +1075,7 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
|
|||||||
export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg) => {
|
export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
const currentState = statePartArg.getState();
|
const currentState = statePartArg.getState();
|
||||||
|
if (!context.identity) return currentState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -1220,8 +1228,9 @@ async function disconnectSocket() {
|
|||||||
// Combined refresh action for efficient polling
|
// Combined refresh action for efficient polling
|
||||||
async function dispatchCombinedRefreshAction() {
|
async function dispatchCombinedRefreshAction() {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
if (!context.identity) return;
|
||||||
const currentView = uiStatePart.getState().activeView;
|
const currentView = uiStatePart.getState().activeView;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Always fetch basic stats for dashboard widgets
|
// Always fetch basic stats for dashboard widgets
|
||||||
const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
@@ -1331,6 +1340,12 @@ async function dispatchCombinedRefreshAction() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Combined refresh failed:', error);
|
console.error('Combined refresh failed:', error);
|
||||||
|
// If the error looks like an auth failure (invalid JWT), force re-login
|
||||||
|
const errMsg = String(error);
|
||||||
|
if (errMsg.includes('invalid') || errMsg.includes('unauthorized') || errMsg.includes('401')) {
|
||||||
|
await loginStatePart.dispatchAction(logoutAction, null);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as appstate from '../appstate.js';
|
import * as appstate from '../appstate.js';
|
||||||
|
import * as interfaces from '../../dist_ts_interfaces/index.js';
|
||||||
import { appRouter } from '../router.js';
|
import { appRouter } from '../router.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -218,13 +219,27 @@ export class OpsDashboard extends DeesElement {
|
|||||||
// Handle initial state - check if we have a stored session that's still valid
|
// Handle initial state - check if we have a stored session that's still valid
|
||||||
const loginState = appstate.loginStatePart.getState();
|
const loginState = appstate.loginStatePart.getState();
|
||||||
if (loginState.identity?.jwt) {
|
if (loginState.identity?.jwt) {
|
||||||
// Verify JWT hasn't expired
|
|
||||||
if (loginState.identity.expiresAt > Date.now()) {
|
if (loginState.identity.expiresAt > Date.now()) {
|
||||||
// JWT still valid, restore logged-in state
|
// Client-side expiry looks valid — verify with server (keypair may have changed)
|
||||||
this.loginState = loginState;
|
try {
|
||||||
await simpleLogin.switchToSlottedContent();
|
const verifyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
interfaces.requests.IReq_VerifyIdentity
|
||||||
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
|
>('/typedrequest', 'verifyIdentity');
|
||||||
|
const response = await verifyRequest.fire({ identity: loginState.identity });
|
||||||
|
if (response.valid) {
|
||||||
|
// JWT confirmed valid by server
|
||||||
|
this.loginState = loginState;
|
||||||
|
await simpleLogin.switchToSlottedContent();
|
||||||
|
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
||||||
|
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
|
||||||
|
} else {
|
||||||
|
// Server rejected the JWT — clear state, show login
|
||||||
|
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Server unreachable or error — clear state, show login
|
||||||
|
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// JWT expired, clear the stored state
|
// JWT expired, clear the stored state
|
||||||
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
||||||
|
|||||||
Reference in New Issue
Block a user