Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| baab152fd3 | |||
| 9baf09ff61 | |||
| 71f23302d3 | |||
| ecbaab3000 | |||
| 8cb1f3c12d | |||
| c7d7f92759 | |||
| 02e1b9231f | |||
| 4ec4dd2bdb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,3 +21,4 @@ dist_*/
|
||||
**/.claude/settings.local.json
|
||||
.nogit/data/
|
||||
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
|
||||
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 |
186
changelog.md
186
changelog.md
@@ -1,5 +1,191 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
bump @push.rocks/smartproxy to ^25.9.1
|
||||
|
||||
- Updated package.json dependency @push.rocks/smartproxy from ^25.9.0 to ^25.9.1
|
||||
- No other code changes; current package version is 10.1.8, recommend a patch release
|
||||
|
||||
## 2026-03-03 - 10.1.8 - fix(deps)
|
||||
bump dependencies: @push.rocks/smartmetrics to ^3.0.2, @push.rocks/smartproxy to ^25.9.0, @serve.zone/remoteingress to ^4.4.0
|
||||
|
||||
- @push.rocks/smartmetrics: 3.0.1 -> 3.0.2 (patch)
|
||||
- @push.rocks/smartproxy: 25.8.5 -> 25.9.0 (minor)
|
||||
- @serve.zone/remoteingress: 4.3.0 -> 4.4.0 (minor)
|
||||
|
||||
## 2026-03-03 - 10.1.7 - fix(ops-view-apitokens)
|
||||
use correct lucide icon name for roll/rotate actions in API tokens view
|
||||
|
||||
- Updated iconName from 'lucide:rotate-cw' to 'lucide:rotateCw' in ts_web/elements/ops-view-apitokens.ts (two occurrences) to match lucide icon naming and ensure icons render correctly
|
||||
- Non-functional UI fix; no API or behavior changes
|
||||
|
||||
## 2026-03-02 - 10.1.6 - fix(ts_web)
|
||||
use actionContext for dispatches in web state actions and bump @push.rocks/smartstate to ^2.2.0
|
||||
|
||||
- Action handlers in ts_web/appstate.ts now accept an actionContext parameter and call await actionContext.dispatch(...) instead of using statePartArg.dispatchAction(...).
|
||||
- Handlers return the awaited dispatch result (ensuring callers receive refreshed state) instead of returning the previous statePartArg.getState().
|
||||
- Dependency bumped in package.json: @push.rocks/smartstate from ^2.1.1 to ^2.2.0.
|
||||
- Playwright artifacts (logs and page screenshots) were added under .playwright-mcp.
|
||||
|
||||
## 2026-03-02 - 10.1.5 - fix(monitoring)
|
||||
use a per-second ring buffer for DNS query metrics, improve DNS logging rate limiting and security event aggregation, and bump smartmta dependency
|
||||
|
||||
|
||||
21
package.json
21
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/dcrouter",
|
||||
"private": false,
|
||||
"version": "10.1.5",
|
||||
"version": "11.0.26",
|
||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -19,21 +19,22 @@
|
||||
"watch": "tswatch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsbuild": "^4.1.15",
|
||||
"@git.zone/tsbundle": "^2.9.0",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.8",
|
||||
"@git.zone/tswatch": "^3.2.0",
|
||||
"@git.zone/tstest": "^3.2.0",
|
||||
"@git.zone/tswatch": "^3.2.5",
|
||||
"@types/node": "^25.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "^3.2.7",
|
||||
"@api.global/typedrequest": "^3.3.0",
|
||||
"@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",
|
||||
"@apiclient.xyz/cloudflare": "^7.1.0",
|
||||
"@design.estate/dees-catalog": "^3.43.3",
|
||||
"@design.estate/dees-element": "^2.1.6",
|
||||
"@push.rocks/lik": "^6.3.1",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@push.rocks/smartacme": "^9.1.3",
|
||||
@@ -43,21 +44,21 @@
|
||||
"@push.rocks/smartguard": "^3.1.0",
|
||||
"@push.rocks/smartjwt": "^2.2.1",
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartmetrics": "^3.0.1",
|
||||
"@push.rocks/smartmetrics": "^3.0.2",
|
||||
"@push.rocks/smartmongo": "^5.1.0",
|
||||
"@push.rocks/smartmta": "^5.3.1",
|
||||
"@push.rocks/smartnetwork": "^4.4.0",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartproxy": "^25.8.5",
|
||||
"@push.rocks/smartproxy": "^25.9.1",
|
||||
"@push.rocks/smartradius": "^1.1.1",
|
||||
"@push.rocks/smartrequest": "^5.0.1",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
"@push.rocks/smartstate": "^2.1.1",
|
||||
"@push.rocks/smartstate": "^2.2.0",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@serve.zone/catalog": "^2.5.0",
|
||||
"@serve.zone/interfaces": "^5.3.0",
|
||||
"@serve.zone/remoteingress": "^4.3.0",
|
||||
"@serve.zone/remoteingress": "^4.4.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"lru-cache": "^11.2.6",
|
||||
"uuid": "^13.0.0"
|
||||
|
||||
2707
pnpm-lock.yaml
generated
2707
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,27 +4,44 @@ import { TypedRequest } from '@api.global/typedrequest';
|
||||
import * as interfaces from '../ts_interfaces/index.js';
|
||||
|
||||
let testDcRouter: DcRouter;
|
||||
let adminIdentity: interfaces.data.IIdentity;
|
||||
|
||||
tap.test('should start DCRouter with OpsServer', async () => {
|
||||
testDcRouter = new DcRouter({
|
||||
// Minimal config for testing
|
||||
cacheConfig: { enabled: false },
|
||||
});
|
||||
|
||||
|
||||
await testDcRouter.start();
|
||||
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 () => {
|
||||
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
|
||||
'http://localhost:3000/typedrequest',
|
||||
'getHealthStatus'
|
||||
);
|
||||
|
||||
|
||||
const response = await healthRequest.fire({
|
||||
detailed: false
|
||||
identity: adminIdentity,
|
||||
detailed: false,
|
||||
});
|
||||
|
||||
|
||||
expect(response).toHaveProperty('health');
|
||||
expect(response.health.healthy).toBeTrue();
|
||||
expect(response.health.services).toHaveProperty('OpsServer');
|
||||
@@ -35,11 +52,12 @@ tap.test('should respond to server statistics request', async () => {
|
||||
'http://localhost:3000/typedrequest',
|
||||
'getServerStatistics'
|
||||
);
|
||||
|
||||
|
||||
const response = await statsRequest.fire({
|
||||
includeHistory: false
|
||||
identity: adminIdentity,
|
||||
includeHistory: false,
|
||||
});
|
||||
|
||||
|
||||
expect(response).toHaveProperty('stats');
|
||||
expect(response.stats).toHaveProperty('uptime');
|
||||
expect(response.stats).toHaveProperty('cpuUsage');
|
||||
@@ -51,9 +69,11 @@ tap.test('should respond to configuration request', async () => {
|
||||
'http://localhost:3000/typedrequest',
|
||||
'getConfiguration'
|
||||
);
|
||||
|
||||
const response = await configRequest.fire({});
|
||||
|
||||
|
||||
const response = await configRequest.fire({
|
||||
identity: adminIdentity,
|
||||
});
|
||||
|
||||
expect(response).toHaveProperty('config');
|
||||
expect(response.config).toHaveProperty('system');
|
||||
expect(response.config).toHaveProperty('smartProxy');
|
||||
@@ -70,19 +90,34 @@ tap.test('should handle log retrieval request', async () => {
|
||||
'http://localhost:3000/typedrequest',
|
||||
'getRecentLogs'
|
||||
);
|
||||
|
||||
|
||||
const response = await logsRequest.fire({
|
||||
limit: 10
|
||||
identity: adminIdentity,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
|
||||
expect(response).toHaveProperty('logs');
|
||||
expect(response).toHaveProperty('total');
|
||||
expect(response).toHaveProperty('hasMore');
|
||||
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 () => {
|
||||
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>(
|
||||
'http://localhost:3000/typedrequest',
|
||||
'getHealthStatus'
|
||||
);
|
||||
|
||||
// No identity provided
|
||||
const response = await healthRequest.fire({});
|
||||
|
||||
expect(response).toHaveProperty('health');
|
||||
expect(response.health.healthy).toBeTrue();
|
||||
console.log('Public endpoint accessible without auth');
|
||||
try {
|
||||
// No identity provided — should be rejected
|
||||
await healthRequest.fire({} as any);
|
||||
expect(true).toBeFalse(); // Should not reach here
|
||||
} catch (error) {
|
||||
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>(
|
||||
'http://localhost:3000/typedrequest',
|
||||
'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.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('radius');
|
||||
expect(response.config).toHaveProperty('remoteIngress');
|
||||
console.log('Configuration read successfully');
|
||||
console.log('Authenticated access to config successful');
|
||||
});
|
||||
|
||||
tap.test('should stop DCRouter', async () => {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '10.1.5',
|
||||
version: '11.0.26',
|
||||
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 paths from '../paths.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 {
|
||||
public dcRouterRef: DcRouter;
|
||||
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();
|
||||
|
||||
|
||||
// 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
|
||||
public adminHandler: handlers.AdminHandler;
|
||||
private configHandler: handlers.ConfigHandler;
|
||||
@@ -25,7 +31,7 @@ export class OpsServer {
|
||||
|
||||
constructor(dcRouterRefArg: DcRouter) {
|
||||
this.dcRouterRef = dcRouterRefArg;
|
||||
|
||||
|
||||
// Add our typedrouter to the dcRouter's main typedrouter
|
||||
this.dcRouterRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
@@ -51,10 +57,25 @@ export class OpsServer {
|
||||
* Set up all TypedRequest handlers
|
||||
*/
|
||||
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);
|
||||
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.logsHandler = new handlers.LogsHandler(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';
|
||||
|
||||
export class ApiTokenHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
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 {
|
||||
// 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
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateApiToken>(
|
||||
'createApiToken',
|
||||
async (dataArg) => {
|
||||
const userId = await this.requireAdmin(dataArg.identity);
|
||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||
if (!manager) {
|
||||
return { success: false, message: 'Token management not initialized' };
|
||||
@@ -39,7 +25,7 @@ export class ApiTokenHandler {
|
||||
dataArg.name,
|
||||
dataArg.scopes,
|
||||
dataArg.expiresInDays ?? null,
|
||||
userId,
|
||||
dataArg.identity.userId,
|
||||
);
|
||||
return { success: true, tokenId: result.id, tokenValue: result.rawToken };
|
||||
},
|
||||
@@ -47,11 +33,10 @@ export class ApiTokenHandler {
|
||||
);
|
||||
|
||||
// List API tokens
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ListApiTokens>(
|
||||
'listApiTokens',
|
||||
async (dataArg) => {
|
||||
await this.requireAdmin(dataArg.identity);
|
||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||
if (!manager) {
|
||||
return { tokens: [] };
|
||||
@@ -62,11 +47,10 @@ export class ApiTokenHandler {
|
||||
);
|
||||
|
||||
// Revoke API token
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RevokeApiToken>(
|
||||
'revokeApiToken',
|
||||
async (dataArg) => {
|
||||
await this.requireAdmin(dataArg.identity);
|
||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||
if (!manager) {
|
||||
return { success: false, message: 'Token management not initialized' };
|
||||
@@ -78,11 +62,10 @@ export class ApiTokenHandler {
|
||||
);
|
||||
|
||||
// Roll API token
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>(
|
||||
'rollApiToken',
|
||||
async (dataArg) => {
|
||||
await this.requireAdmin(dataArg.identity);
|
||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||
if (!manager) {
|
||||
return { success: false, message: 'Token management not initialized' };
|
||||
@@ -97,11 +80,10 @@ export class ApiTokenHandler {
|
||||
);
|
||||
|
||||
// Toggle API token
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
|
||||
'toggleApiToken',
|
||||
async (dataArg) => {
|
||||
await this.requireAdmin(dataArg.identity);
|
||||
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||
if (!manager) {
|
||||
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';
|
||||
|
||||
export class CertificateHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
const viewRouter = this.opsServerRef.viewRouter;
|
||||
const adminRouter = this.opsServerRef.adminRouter;
|
||||
|
||||
// ---- Read endpoints (viewRouter — valid identity required via middleware) ----
|
||||
|
||||
// Get Certificate Overview
|
||||
this.typedrouter.addTypedHandler(
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCertificateOverview>(
|
||||
'getCertificateOverview',
|
||||
async (dataArg) => {
|
||||
@@ -23,8 +25,10 @@ export class CertificateHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// ---- Write endpoints (adminRouter — admin identity required via middleware) ----
|
||||
|
||||
// Legacy route-based reprovision (backward compat)
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificate>(
|
||||
'reprovisionCertificate',
|
||||
async (dataArg) => {
|
||||
@@ -34,7 +38,7 @@ export class CertificateHandler {
|
||||
);
|
||||
|
||||
// Domain-based reprovision (preferred)
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>(
|
||||
'reprovisionCertificateDomain',
|
||||
async (dataArg) => {
|
||||
@@ -44,7 +48,7 @@ export class CertificateHandler {
|
||||
);
|
||||
|
||||
// Delete certificate
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteCertificate>(
|
||||
'deleteCertificate',
|
||||
async (dataArg) => {
|
||||
@@ -54,7 +58,7 @@ export class CertificateHandler {
|
||||
);
|
||||
|
||||
// Export certificate
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportCertificate>(
|
||||
'exportCertificate',
|
||||
async (dataArg) => {
|
||||
@@ -64,7 +68,7 @@ export class CertificateHandler {
|
||||
);
|
||||
|
||||
// Import certificate
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportCertificate>(
|
||||
'importCertificate',
|
||||
async (dataArg) => {
|
||||
|
||||
@@ -4,17 +4,16 @@ import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
|
||||
export class ConfigHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
// Config endpoint registers directly on viewRouter (valid identity required via middleware)
|
||||
const router = this.opsServerRef.viewRouter;
|
||||
|
||||
// Get Configuration Handler (read-only)
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>(
|
||||
'getConfiguration',
|
||||
async (dataArg, toolsArg) => {
|
||||
|
||||
@@ -3,17 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
|
||||
export class EmailOpsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
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
|
||||
this.typedrouter.addTypedHandler(
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAllEmails>(
|
||||
'getAllEmails',
|
||||
async (dataArg) => {
|
||||
@@ -24,7 +25,7 @@ export class EmailOpsHandler {
|
||||
);
|
||||
|
||||
// Get Email Detail Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailDetail>(
|
||||
'getEmailDetail',
|
||||
async (dataArg) => {
|
||||
@@ -34,8 +35,10 @@ export class EmailOpsHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// ---- Write endpoints (adminRouter) ----
|
||||
|
||||
// Resend Failed Email Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ResendEmail>(
|
||||
'resendEmail',
|
||||
async (dataArg) => {
|
||||
|
||||
@@ -10,12 +10,9 @@ let logPushDestinationInstalled = false;
|
||||
let currentOpsServerRef: OpsServer | null = null;
|
||||
|
||||
export class LogsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
private activeStreamStops: Set<() => void> = new Set();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
this.setupLogPushDestination();
|
||||
}
|
||||
@@ -35,8 +32,11 @@ export class LogsHandler {
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
// All log endpoints register directly on viewRouter (valid identity required via middleware)
|
||||
const router = this.opsServerRef.viewRouter;
|
||||
|
||||
// Get Recent Logs Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRecentLogs>(
|
||||
'getRecentLogs',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -59,7 +59,7 @@ export class LogsHandler {
|
||||
);
|
||||
|
||||
// Get Log Stream Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetLogStream>(
|
||||
'getLogStream',
|
||||
async (dataArg, toolsArg) => {
|
||||
|
||||
@@ -3,21 +3,19 @@ import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
|
||||
export class RadiusHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
const viewRouter = this.opsServerRef.viewRouter;
|
||||
const adminRouter = this.opsServerRef.adminRouter;
|
||||
// ========================================================================
|
||||
// RADIUS Client Management
|
||||
// ========================================================================
|
||||
|
||||
// Get all RADIUS clients
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get all RADIUS clients (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusClients>(
|
||||
'getRadiusClients',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -40,8 +38,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Add or update a RADIUS client
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Add or update a RADIUS client (write)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRadiusClient>(
|
||||
'setRadiusClient',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -61,8 +59,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Remove a RADIUS client
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Remove a RADIUS client (write)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRadiusClient>(
|
||||
'removeRadiusClient',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -85,8 +83,8 @@ export class RadiusHandler {
|
||||
// VLAN Mapping Management
|
||||
// ========================================================================
|
||||
|
||||
// Get all VLAN mappings
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get all VLAN mappings (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVlanMappings>(
|
||||
'getVlanMappings',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -121,8 +119,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Add or update a VLAN mapping
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Add or update a VLAN mapping (write)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetVlanMapping>(
|
||||
'setVlanMapping',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -153,8 +151,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Remove a VLAN mapping
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Remove a VLAN mapping (write)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveVlanMapping>(
|
||||
'removeVlanMapping',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -174,8 +172,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Update VLAN configuration
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Update VLAN configuration (write)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVlanConfig>(
|
||||
'updateVlanConfig',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -206,8 +204,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Test VLAN assignment
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Test VLAN assignment (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestVlanAssignment>(
|
||||
'testVlanAssignment',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -240,8 +238,8 @@ export class RadiusHandler {
|
||||
// Accounting / Session Management
|
||||
// ========================================================================
|
||||
|
||||
// Get active sessions
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get active sessions (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusSessions>(
|
||||
'getRadiusSessions',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -289,8 +287,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Disconnect a session
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Disconnect a session (write)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisconnectRadiusSession>(
|
||||
'disconnectRadiusSession',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -314,8 +312,8 @@ export class RadiusHandler {
|
||||
)
|
||||
);
|
||||
|
||||
// Get accounting summary
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get accounting summary (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusAccountingSummary>(
|
||||
'getRadiusAccountingSummary',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -351,8 +349,8 @@ export class RadiusHandler {
|
||||
// Statistics
|
||||
// ========================================================================
|
||||
|
||||
// Get RADIUS statistics
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get RADIUS statistics (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusStatistics>(
|
||||
'getRadiusStatistics',
|
||||
async (dataArg, toolsArg) => {
|
||||
|
||||
@@ -3,16 +3,18 @@ import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
|
||||
export class RemoteIngressHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
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
|
||||
this.typedrouter.addTypedHandler(
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>(
|
||||
'getRemoteIngresses',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -36,8 +38,10 @@ export class RemoteIngressHandler {
|
||||
),
|
||||
);
|
||||
|
||||
// ---- Write endpoints (adminRouter) ----
|
||||
|
||||
// Create a new remote ingress edge
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>(
|
||||
'createRemoteIngress',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -69,7 +73,7 @@ export class RemoteIngressHandler {
|
||||
);
|
||||
|
||||
// Delete a remote ingress edge
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>(
|
||||
'deleteRemoteIngress',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -94,7 +98,7 @@ export class RemoteIngressHandler {
|
||||
);
|
||||
|
||||
// Update a remote ingress edge
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>(
|
||||
'updateRemoteIngress',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -138,7 +142,7 @@ export class RemoteIngressHandler {
|
||||
);
|
||||
|
||||
// Regenerate secret for an edge
|
||||
this.typedrouter.addTypedHandler(
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
|
||||
'regenerateRemoteIngressSecret',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -164,8 +168,8 @@ export class RemoteIngressHandler {
|
||||
),
|
||||
);
|
||||
|
||||
// Get runtime status of all edges
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get runtime status of all edges (read)
|
||||
viewRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>(
|
||||
'getRemoteIngressStatus',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -178,8 +182,8 @@ export class RemoteIngressHandler {
|
||||
),
|
||||
);
|
||||
|
||||
// Get a connection token for an edge
|
||||
this.typedrouter.addTypedHandler(
|
||||
// Get a connection token for an edge (write — exposes secret)
|
||||
adminRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressConnectionToken>(
|
||||
'getRemoteIngressConnectionToken',
|
||||
async (dataArg, toolsArg) => {
|
||||
|
||||
@@ -4,17 +4,16 @@ import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
import { MetricsManager } from '../../monitoring/index.js';
|
||||
|
||||
export class SecurityHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
|
||||
private registerHandlers(): void {
|
||||
// All security endpoints register directly on viewRouter (valid identity required via middleware)
|
||||
const router = this.opsServerRef.viewRouter;
|
||||
|
||||
// Security Metrics Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetSecurityMetrics>(
|
||||
'getSecurityMetrics',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -40,7 +39,7 @@ export class SecurityHandler {
|
||||
);
|
||||
|
||||
// Active Connections Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetActiveConnections>(
|
||||
'getActiveConnections',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -77,8 +76,8 @@ export class SecurityHandler {
|
||||
);
|
||||
|
||||
// Network Stats Handler - provides comprehensive network metrics
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetNetworkStats>(
|
||||
'getNetworkStats',
|
||||
async (dataArg, toolsArg) => {
|
||||
// Get network stats from MetricsManager if available
|
||||
@@ -121,7 +120,7 @@ export class SecurityHandler {
|
||||
);
|
||||
|
||||
// Rate Limit Status Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRateLimitStatus>(
|
||||
'getRateLimitStatus',
|
||||
async (dataArg, toolsArg) => {
|
||||
|
||||
@@ -5,17 +5,16 @@ import { MetricsManager } from '../../monitoring/index.js';
|
||||
import { SecurityLogger } from '../../security/classes.securitylogger.js';
|
||||
|
||||
export class StatsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
|
||||
private registerHandlers(): void {
|
||||
// All stats endpoints register directly on viewRouter (valid identity required via middleware)
|
||||
const router = this.opsServerRef.viewRouter;
|
||||
|
||||
// Server Statistics Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServerStatistics>(
|
||||
'getServerStatistics',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -38,7 +37,7 @@ export class StatsHandler {
|
||||
);
|
||||
|
||||
// Email Statistics Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetEmailStatistics>(
|
||||
'getEmailStatistics',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -77,7 +76,7 @@ export class StatsHandler {
|
||||
);
|
||||
|
||||
// DNS Statistics Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetDnsStatistics>(
|
||||
'getDnsStatistics',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -114,7 +113,7 @@ export class StatsHandler {
|
||||
);
|
||||
|
||||
// Queue Status Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetQueueStatus>(
|
||||
'getQueueStatus',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -142,7 +141,7 @@ export class StatsHandler {
|
||||
);
|
||||
|
||||
// Health Status Handler
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHealthStatus>(
|
||||
'getHealthStatus',
|
||||
async (dataArg, toolsArg) => {
|
||||
@@ -167,7 +166,7 @@ export class StatsHandler {
|
||||
);
|
||||
|
||||
// Combined Metrics Handler - More efficient for frontend polling
|
||||
this.typedrouter.addTypedHandler(
|
||||
router.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetCombinedMetrics>(
|
||||
'getCombinedMetrics',
|
||||
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,
|
||||
dataArg: T
|
||||
dataArg: { identity?: interfaces.data.IIdentity }
|
||||
): Promise<void> {
|
||||
if (!dataArg.identity) {
|
||||
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
||||
}
|
||||
|
||||
|
||||
const passed = await adminHandler.adminIdentityGuard.exec({ identity: dataArg.identity });
|
||||
if (!passed) {
|
||||
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,
|
||||
dataArg: T
|
||||
dataArg: { identity?: interfaces.data.IIdentity }
|
||||
): Promise<void> {
|
||||
if (!dataArg.identity) {
|
||||
throw new plugins.typedrequest.TypedResponseError('No identity provided');
|
||||
}
|
||||
|
||||
|
||||
const passed = await adminHandler.validIdentityGuard.exec({ identity: dataArg.identity });
|
||||
if (!passed) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Valid identity required');
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface IReq_CreateApiToken extends plugins.typedrequestInterfaces.impl
|
||||
> {
|
||||
method: 'createApiToken';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
name: string;
|
||||
scopes: TApiTokenScope[];
|
||||
expiresInDays?: number | null;
|
||||
@@ -38,7 +38,7 @@ export interface IReq_ListApiTokens extends plugins.typedrequestInterfaces.imple
|
||||
> {
|
||||
method: 'listApiTokens';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
tokens: IApiTokenInfo[];
|
||||
@@ -54,7 +54,7 @@ export interface IReq_RevokeApiToken extends plugins.typedrequestInterfaces.impl
|
||||
> {
|
||||
method: 'revokeApiToken';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
};
|
||||
response: {
|
||||
@@ -73,7 +73,7 @@ export interface IReq_RollApiToken extends plugins.typedrequestInterfaces.implem
|
||||
> {
|
||||
method: 'rollApiToken';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
};
|
||||
response: {
|
||||
@@ -92,7 +92,7 @@ export interface IReq_ToggleApiToken extends plugins.typedrequestInterfaces.impl
|
||||
> {
|
||||
method: 'toggleApiToken';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface IReq_GetCertificateOverview extends plugins.typedrequestInterfa
|
||||
> {
|
||||
method: 'getCertificateOverview';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
certificates: ICertificateInfo[];
|
||||
@@ -50,7 +50,7 @@ export interface IReq_ReprovisionCertificate extends plugins.typedrequestInterfa
|
||||
> {
|
||||
method: 'reprovisionCertificate';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
routeName: string;
|
||||
};
|
||||
response: {
|
||||
@@ -66,7 +66,7 @@ export interface IReq_ReprovisionCertificateDomain extends plugins.typedrequestI
|
||||
> {
|
||||
method: 'reprovisionCertificateDomain';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
domain: string;
|
||||
};
|
||||
response: {
|
||||
@@ -82,7 +82,7 @@ export interface IReq_DeleteCertificate extends plugins.typedrequestInterfaces.i
|
||||
> {
|
||||
method: 'deleteCertificate';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
domain: string;
|
||||
};
|
||||
response: {
|
||||
@@ -98,7 +98,7 @@ export interface IReq_ExportCertificate extends plugins.typedrequestInterfaces.i
|
||||
> {
|
||||
method: 'exportCertificate';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
domain: string;
|
||||
};
|
||||
response: {
|
||||
@@ -123,7 +123,7 @@ export interface IReq_ImportCertificate extends plugins.typedrequestInterfaces.i
|
||||
> {
|
||||
method: 'importCertificate';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
cert: {
|
||||
id: string;
|
||||
domainName: string;
|
||||
|
||||
@@ -81,7 +81,7 @@ export interface IReq_GetConfiguration extends plugins.typedrequestInterfaces.im
|
||||
> {
|
||||
method: 'getConfiguration';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
section?: string;
|
||||
};
|
||||
response: {
|
||||
|
||||
@@ -68,7 +68,7 @@ export interface IReq_GetAllEmails extends plugins.typedrequestInterfaces.implem
|
||||
> {
|
||||
method: 'getAllEmails';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
emails: IEmail[];
|
||||
@@ -84,7 +84,7 @@ export interface IReq_GetEmailDetail extends plugins.typedrequestInterfaces.impl
|
||||
> {
|
||||
method: 'getEmailDetail';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
emailId: string;
|
||||
};
|
||||
response: {
|
||||
@@ -101,7 +101,7 @@ export interface IReq_ResendEmail extends plugins.typedrequestInterfaces.impleme
|
||||
> {
|
||||
method: 'resendEmail';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
emailId: string;
|
||||
};
|
||||
response: {
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface IReq_GetRecentLogs extends plugins.typedrequestInterfaces.imple
|
||||
> {
|
||||
method: 'getRecentLogs';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
level?: 'debug' | 'info' | 'warn' | 'error';
|
||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||
limit?: number;
|
||||
@@ -31,7 +31,7 @@ export interface IReq_GetLogStream extends plugins.typedrequestInterfaces.implem
|
||||
> {
|
||||
method: 'getLogStream';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
follow?: boolean;
|
||||
filters?: {
|
||||
level?: string[];
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface IReq_GetRadiusClients extends plugins.typedrequestInterfaces.im
|
||||
> {
|
||||
method: 'getRadiusClients';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
clients: Array<{
|
||||
@@ -35,7 +35,7 @@ export interface IReq_SetRadiusClient extends plugins.typedrequestInterfaces.imp
|
||||
> {
|
||||
method: 'setRadiusClient';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
client: {
|
||||
name: string;
|
||||
ipRange: string;
|
||||
@@ -59,7 +59,7 @@ export interface IReq_RemoveRadiusClient extends plugins.typedrequestInterfaces.
|
||||
> {
|
||||
method: 'removeRadiusClient';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
name: string;
|
||||
};
|
||||
response: {
|
||||
@@ -81,7 +81,7 @@ export interface IReq_GetVlanMappings extends plugins.typedrequestInterfaces.imp
|
||||
> {
|
||||
method: 'getVlanMappings';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
mappings: Array<{
|
||||
@@ -108,7 +108,7 @@ export interface IReq_SetVlanMapping extends plugins.typedrequestInterfaces.impl
|
||||
> {
|
||||
method: 'setVlanMapping';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
mapping: {
|
||||
mac: string;
|
||||
vlan: number;
|
||||
@@ -139,7 +139,7 @@ export interface IReq_RemoveVlanMapping extends plugins.typedrequestInterfaces.i
|
||||
> {
|
||||
method: 'removeVlanMapping';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
mac: string;
|
||||
};
|
||||
response: {
|
||||
@@ -157,7 +157,7 @@ export interface IReq_UpdateVlanConfig extends plugins.typedrequestInterfaces.im
|
||||
> {
|
||||
method: 'updateVlanConfig';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
defaultVlan?: number;
|
||||
allowUnknownMacs?: boolean;
|
||||
};
|
||||
@@ -179,7 +179,7 @@ export interface IReq_TestVlanAssignment extends plugins.typedrequestInterfaces.
|
||||
> {
|
||||
method: 'testVlanAssignment';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
mac: string;
|
||||
};
|
||||
response: {
|
||||
@@ -207,7 +207,7 @@ export interface IReq_GetRadiusSessions extends plugins.typedrequestInterfaces.i
|
||||
> {
|
||||
method: 'getRadiusSessions';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
filter?: {
|
||||
username?: string;
|
||||
nasIpAddress?: string;
|
||||
@@ -243,7 +243,7 @@ export interface IReq_DisconnectRadiusSession extends plugins.typedrequestInterf
|
||||
> {
|
||||
method: 'disconnectRadiusSession';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
sessionId: string;
|
||||
reason?: string;
|
||||
};
|
||||
@@ -262,7 +262,7 @@ export interface IReq_GetRadiusAccountingSummary extends plugins.typedrequestInt
|
||||
> {
|
||||
method: 'getRadiusAccountingSummary';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
@@ -296,7 +296,7 @@ export interface IReq_GetRadiusStatistics extends plugins.typedrequestInterfaces
|
||||
> {
|
||||
method: 'getRadiusStatistics';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
stats: {
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces
|
||||
> {
|
||||
method: 'createRemoteIngress';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
name: string;
|
||||
listenPorts?: number[];
|
||||
autoDerivePorts?: boolean;
|
||||
@@ -36,7 +36,7 @@ export interface IReq_DeleteRemoteIngress extends plugins.typedrequestInterfaces
|
||||
> {
|
||||
method: 'deleteRemoteIngress';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
};
|
||||
response: {
|
||||
@@ -54,7 +54,7 @@ export interface IReq_UpdateRemoteIngress extends plugins.typedrequestInterfaces
|
||||
> {
|
||||
method: 'updateRemoteIngress';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
name?: string;
|
||||
listenPorts?: number[];
|
||||
@@ -77,7 +77,7 @@ export interface IReq_RegenerateRemoteIngressSecret extends plugins.typedrequest
|
||||
> {
|
||||
method: 'regenerateRemoteIngressSecret';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
id: string;
|
||||
};
|
||||
response: {
|
||||
@@ -95,7 +95,7 @@ export interface IReq_GetRemoteIngresses extends plugins.typedrequestInterfaces.
|
||||
> {
|
||||
method: 'getRemoteIngresses';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
edges: IRemoteIngress[];
|
||||
@@ -111,7 +111,7 @@ export interface IReq_GetRemoteIngressStatus extends plugins.typedrequestInterfa
|
||||
> {
|
||||
method: 'getRemoteIngressStatus';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
};
|
||||
response: {
|
||||
statuses: IRemoteIngressStatus[];
|
||||
@@ -128,7 +128,7 @@ export interface IReq_GetRemoteIngressConnectionToken extends plugins.typedreque
|
||||
> {
|
||||
method: 'getRemoteIngressConnectionToken';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
edgeId: string;
|
||||
hubHost?: string;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface IReq_GetServerStatistics extends plugins.typedrequestInterfaces
|
||||
> {
|
||||
method: 'getServerStatistics';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
includeHistory?: boolean;
|
||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||
};
|
||||
@@ -29,7 +29,7 @@ export interface IReq_GetEmailStatistics extends plugins.typedrequestInterfaces.
|
||||
> {
|
||||
method: 'getEmailStatistics';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||
domain?: string;
|
||||
includeDetails?: boolean;
|
||||
@@ -49,7 +49,7 @@ export interface IReq_GetDnsStatistics extends plugins.typedrequestInterfaces.im
|
||||
> {
|
||||
method: 'getDnsStatistics';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||
domain?: string;
|
||||
includeQueryTypes?: boolean;
|
||||
@@ -69,7 +69,7 @@ export interface IReq_GetRateLimitStatus extends plugins.typedrequestInterfaces.
|
||||
> {
|
||||
method: 'getRateLimitStatus';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
domain?: string;
|
||||
ip?: string;
|
||||
includeBlocked?: boolean;
|
||||
@@ -91,7 +91,7 @@ export interface IReq_GetSecurityMetrics extends plugins.typedrequestInterfaces.
|
||||
> {
|
||||
method: 'getSecurityMetrics';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
timeRange?: '1h' | '6h' | '24h' | '7d' | '30d';
|
||||
includeDetails?: boolean;
|
||||
};
|
||||
@@ -112,7 +112,7 @@ export interface IReq_GetActiveConnections extends plugins.typedrequestInterface
|
||||
> {
|
||||
method: 'getActiveConnections';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
protocol?: 'smtp' | 'smtps' | 'http' | 'https';
|
||||
state?: string;
|
||||
};
|
||||
@@ -137,7 +137,7 @@ export interface IReq_GetQueueStatus extends plugins.typedrequestInterfaces.impl
|
||||
> {
|
||||
method: 'getQueueStatus';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
queueName?: string;
|
||||
};
|
||||
response: {
|
||||
@@ -153,10 +153,31 @@ export interface IReq_GetHealthStatus extends plugins.typedrequestInterfaces.imp
|
||||
> {
|
||||
method: 'getHealthStatus';
|
||||
request: {
|
||||
identity?: authInterfaces.IIdentity;
|
||||
identity: authInterfaces.IIdentity;
|
||||
detailed?: boolean;
|
||||
};
|
||||
response: {
|
||||
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 = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '10.1.5',
|
||||
version: '11.0.26',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -238,9 +238,12 @@ interface IActionContext {
|
||||
}
|
||||
|
||||
const getActionContext = (): IActionContext => {
|
||||
return {
|
||||
identity: loginStatePart.getState().identity,
|
||||
};
|
||||
const 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
|
||||
@@ -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) => {
|
||||
const context = getActionContext();
|
||||
if (!context.identity) return statePartArg.getState();
|
||||
|
||||
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_AdminLogout
|
||||
>('/typedrequest', 'adminLogout');
|
||||
|
||||
try {
|
||||
await typedRequest.fire({
|
||||
identity: context.identity,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
// Try to notify server, but don't block logout if identity is missing/expired
|
||||
if (context.identity) {
|
||||
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_AdminLogout
|
||||
>('/typedrequest', 'adminLogout');
|
||||
try {
|
||||
await typedRequest.fire({ identity: context.identity });
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear login state regardless
|
||||
// Always clear login state
|
||||
return {
|
||||
identity: null,
|
||||
isLoggedIn: false,
|
||||
@@ -298,8 +300,8 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
|
||||
// Fetch All Stats Action - Using combined endpoint for efficiency
|
||||
export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
// 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)
|
||||
export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
@@ -373,6 +375,7 @@ export const fetchRecentLogsAction = logStatePart.createAction<{
|
||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
const context = getActionContext();
|
||||
if (!context.identity) return statePartArg.getState();
|
||||
|
||||
const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_GetRecentLogs
|
||||
@@ -448,8 +451,8 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
||||
// Fetch Network Stats Action
|
||||
export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
// 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) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
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) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
@@ -581,7 +586,7 @@ export const fetchCertificateOverviewAction = certificateStatePart.createAction(
|
||||
});
|
||||
|
||||
export const reprovisionCertificateAction = certificateStatePart.createAction<string>(
|
||||
async (statePartArg, domain) => {
|
||||
async (statePartArg, domain, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -596,8 +601,7 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
|
||||
});
|
||||
|
||||
// Re-fetch overview after reprovisioning
|
||||
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchCertificateOverviewAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -608,7 +612,7 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
|
||||
);
|
||||
|
||||
export const deleteCertificateAction = certificateStatePart.createAction<string>(
|
||||
async (statePartArg, domain) => {
|
||||
async (statePartArg, domain, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -623,8 +627,7 @@ export const deleteCertificateAction = certificateStatePart.createAction<string>
|
||||
});
|
||||
|
||||
// Re-fetch overview after deletion
|
||||
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchCertificateOverviewAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -643,7 +646,7 @@ export const importCertificateAction = certificateStatePart.createAction<{
|
||||
publicKey: string;
|
||||
csr: string;
|
||||
}>(
|
||||
async (statePartArg, cert) => {
|
||||
async (statePartArg, cert, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -658,8 +661,7 @@ export const importCertificateAction = certificateStatePart.createAction<{
|
||||
});
|
||||
|
||||
// Re-fetch overview after import
|
||||
await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchCertificateOverviewAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -700,6 +702,7 @@ export async function fetchConnectionToken(edgeId: string) {
|
||||
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
@@ -737,7 +740,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
listenPorts?: number[];
|
||||
autoDerivePorts?: boolean;
|
||||
tags?: string[];
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -756,7 +759,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
|
||||
if (response.success) {
|
||||
// Refresh the list
|
||||
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||
await actionContext.dispatch(fetchRemoteIngressAction, null);
|
||||
|
||||
return {
|
||||
...statePartArg.getState(),
|
||||
@@ -774,7 +777,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
});
|
||||
|
||||
export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>(
|
||||
async (statePartArg, edgeId) => {
|
||||
async (statePartArg, edgeId, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -788,8 +791,7 @@ export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<str
|
||||
id: edgeId,
|
||||
});
|
||||
|
||||
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchRemoteIngressAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -805,7 +807,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
listenPorts?: number[];
|
||||
autoDerivePorts?: boolean;
|
||||
tags?: string[];
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -823,8 +825,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
tags: dataArg.tags,
|
||||
});
|
||||
|
||||
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchRemoteIngressAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -877,7 +878,7 @@ export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
|
||||
export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -892,8 +893,7 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
enabled: dataArg.enabled,
|
||||
});
|
||||
|
||||
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchRemoteIngressAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -909,6 +909,7 @@ export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
@@ -939,7 +940,7 @@ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(asy
|
||||
export const createRouteAction = routeManagementStatePart.createAction<{
|
||||
route: any;
|
||||
enabled?: boolean;
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -954,8 +955,7 @@ export const createRouteAction = routeManagementStatePart.createAction<{
|
||||
enabled: dataArg.enabled,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchMergedRoutesAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -965,7 +965,7 @@ export const createRouteAction = routeManagementStatePart.createAction<{
|
||||
});
|
||||
|
||||
export const deleteRouteAction = routeManagementStatePart.createAction<string>(
|
||||
async (statePartArg, routeId) => {
|
||||
async (statePartArg, routeId, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -979,8 +979,7 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
|
||||
id: routeId,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchMergedRoutesAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -993,7 +992,7 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
|
||||
export const toggleRouteAction = routeManagementStatePart.createAction<{
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -1008,8 +1007,7 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
|
||||
enabled: dataArg.enabled,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchMergedRoutesAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -1021,7 +1019,7 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
|
||||
export const setRouteOverrideAction = routeManagementStatePart.createAction<{
|
||||
routeName: string;
|
||||
enabled: boolean;
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -1036,8 +1034,7 @@ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
|
||||
enabled: dataArg.enabled,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchMergedRoutesAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -1047,7 +1044,7 @@ export const setRouteOverrideAction = routeManagementStatePart.createAction<{
|
||||
});
|
||||
|
||||
export const removeRouteOverrideAction = routeManagementStatePart.createAction<string>(
|
||||
async (statePartArg, routeName) => {
|
||||
async (statePartArg, routeName, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -1061,8 +1058,7 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
|
||||
routeName,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchMergedRoutesAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -1079,6 +1075,7 @@ export const removeRouteOverrideAction = routeManagementStatePart.createAction<s
|
||||
export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
if (!context.identity) return currentState;
|
||||
|
||||
try {
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
@@ -1128,7 +1125,7 @@ export async function rollApiToken(id: string) {
|
||||
}
|
||||
|
||||
export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
|
||||
async (statePartArg, tokenId) => {
|
||||
async (statePartArg, tokenId, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -1142,8 +1139,7 @@ export const revokeApiTokenAction = routeManagementStatePart.createAction<string
|
||||
id: tokenId,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchApiTokensAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchApiTokensAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -1156,7 +1152,7 @@ export const revokeApiTokenAction = routeManagementStatePart.createAction<string
|
||||
export const toggleApiTokenAction = routeManagementStatePart.createAction<{
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
}>(async (statePartArg, dataArg) => {
|
||||
}>(async (statePartArg, dataArg, actionContext) => {
|
||||
const context = getActionContext();
|
||||
const currentState = statePartArg.getState();
|
||||
|
||||
@@ -1171,8 +1167,7 @@ export const toggleApiTokenAction = routeManagementStatePart.createAction<{
|
||||
enabled: dataArg.enabled,
|
||||
});
|
||||
|
||||
await routeManagementStatePart.dispatchAction(fetchApiTokensAction, null);
|
||||
return statePartArg.getState();
|
||||
return await actionContext.dispatch(fetchApiTokensAction, null);
|
||||
} catch (error) {
|
||||
return {
|
||||
...currentState,
|
||||
@@ -1233,8 +1228,9 @@ async function disconnectSocket() {
|
||||
// Combined refresh action for efficient polling
|
||||
async function dispatchCombinedRefreshAction() {
|
||||
const context = getActionContext();
|
||||
if (!context.identity) return;
|
||||
const currentView = uiStatePart.getState().activeView;
|
||||
|
||||
|
||||
try {
|
||||
// Always fetch basic stats for dashboard widgets
|
||||
const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
@@ -1344,6 +1340,12 @@ async function dispatchCombinedRefreshAction() {
|
||||
}
|
||||
} catch (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 appstate from '../appstate.js';
|
||||
import * as interfaces from '../../dist_ts_interfaces/index.js';
|
||||
import { appRouter } from '../router.js';
|
||||
|
||||
import {
|
||||
@@ -218,13 +219,27 @@ export class OpsDashboard extends DeesElement {
|
||||
// Handle initial state - check if we have a stored session that's still valid
|
||||
const loginState = appstate.loginStatePart.getState();
|
||||
if (loginState.identity?.jwt) {
|
||||
// Verify JWT hasn't expired
|
||||
if (loginState.identity.expiresAt > Date.now()) {
|
||||
// JWT still valid, restore logged-in state
|
||||
this.loginState = loginState;
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
||||
await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
|
||||
// Client-side expiry looks valid — verify with server (keypair may have changed)
|
||||
try {
|
||||
const verifyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_VerifyIdentity
|
||||
>('/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 {
|
||||
// JWT expired, clear the stored state
|
||||
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
||||
|
||||
@@ -154,7 +154,7 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
},
|
||||
{
|
||||
name: 'Roll',
|
||||
iconName: 'lucide:rotate-cw',
|
||||
iconName: 'lucide:rotateCw',
|
||||
type: ['inRow', 'contextmenu'] as any,
|
||||
actionFunc: async (actionData: any) => {
|
||||
const token = actionData.item as interfaces.data.IApiTokenInfo;
|
||||
@@ -306,7 +306,7 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
},
|
||||
{
|
||||
name: 'Roll Token',
|
||||
iconName: 'lucide:rotate-cw',
|
||||
iconName: 'lucide:rotateCw',
|
||||
action: async (modalArg: any) => {
|
||||
await modalArg.destroy();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user