Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2530918dc6 | |||
| 0b09ea1573 | |||
| 21157477b4 | |||
| fcf36e5cd5 | |||
| f5740fa565 | |||
| 4a9fba53a9 | |||
| da61adc9a2 | |||
| 616066ffd0 | |||
| bd5cccb405 | |||
| fbade85cda | |||
| 9060d26f3a | |||
| c889141ec3 | |||
| fb472f353c | |||
| 090bd747e1 | |||
| 4d77a94bbb | |||
| 7f5284b10f | |||
| 9cd5db2d81 | |||
| de0b7d1fe0 |
64
changelog.md
64
changelog.md
@@ -1,5 +1,69 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.4.5 - fix(remoteingress)
|
||||||
|
mark remote ingress data actions as row actions and bump @design.estate/dees-catalog dependency
|
||||||
|
|
||||||
|
- Add type:['row'] to 'Regenerate Secret' and 'Delete' dataActions in ts_web/elements/ops-view-remoteingress.ts to ensure they are treated as row actions in the UI
|
||||||
|
- Bump @design.estate/dees-catalog from ^3.42.0 to ^3.42.2 in package.json
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.4.4 - fix(deps)
|
||||||
|
bump @push.rocks/smartproxy to ^25.7.3
|
||||||
|
|
||||||
|
- Updated @push.rocks/smartproxy from ^25.7.2 to ^25.7.3 in package.json
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.4.3 - fix(deps)
|
||||||
|
bump @push.rocks/smartproxy to ^25.7.2
|
||||||
|
|
||||||
|
- Updated package.json: @push.rocks/smartproxy ^25.7.1 -> ^25.7.2 (patch dependency update)
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.4.2 - fix(smartproxy)
|
||||||
|
bump @push.rocks/smartproxy to ^25.7.1
|
||||||
|
|
||||||
|
- Updated dependency @push.rocks/smartproxy from ^25.7.0 to ^25.7.1 in package.json
|
||||||
|
- No other source changes; dependency patch bump only
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.4.1 - fix(deps)
|
||||||
|
bump dependencies: @push.rocks/smartproxy to ^25.7.0 and @serve.zone/remoteingress to ^3.0.2
|
||||||
|
|
||||||
|
- Bumped @push.rocks/smartproxy from ^25.5.0 to ^25.7.0
|
||||||
|
- Bumped @serve.zone/remoteingress from ^3.0.1 to ^3.0.2
|
||||||
|
- Package current version is 6.4.0 — recommended patch release
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.4.0 - feat(remoteingress)
|
||||||
|
add Remote Ingress hub and management for edge tunnel nodes, including backend managers, tunnel hub integration, opsserver handlers, typedrequest APIs, and web UI
|
||||||
|
|
||||||
|
- Introduce RemoteIngressManager for CRUD and persistent storage of edge registrations
|
||||||
|
- Introduce TunnelManager to run the RemoteIngressHub, track connected edge statuses, and sync allowed edges to the hub
|
||||||
|
- Integrate remote ingress into DcRouter (options.remoteIngressConfig, setupRemoteIngress, startup/shutdown handling, and startup summary)
|
||||||
|
- Add OpsServer RemoteIngressHandler exposing typedrequest APIs (create/update/delete/regenerate/get/status)
|
||||||
|
- Add web UI: Remote Ingress view, app state parts, actions and components to manage edges and display runtime statuses
|
||||||
|
- Add typedrequest and data interfaces for remoteingress and export the remoteingress module; add @serve.zone/remoteingress dependency in package.json
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.3.0 - feat(dcrouter)
|
||||||
|
add configurable baseDir and centralized path resolution; use resolved data paths for storage, cache and DNS
|
||||||
|
|
||||||
|
- Introduce IDcRouterOptions.baseDir to allow configuring base directory for dcrouter data (defaults to ~/.serve.zone/dcrouter).
|
||||||
|
- Add DcRouter.resolvedPaths and resolvePaths(baseDir) in ts/paths.ts to centralize computation of dcrouterHomeDir, dataDir, defaultTsmDbPath, defaultStoragePath and dnsRecordsDir.
|
||||||
|
- Use resolvedPaths throughout DcRouter: default filesystem storage fsPath, CacheDb storagePath, and DNS records loading now reference resolved paths.
|
||||||
|
- Replace ensureDirectories() behavior with ensureDataDirectories(resolvedPaths) to only create data-related directories; keep legacy ensureDirectories wrapper delegating to the new function.
|
||||||
|
- Simplify paths module by removing unused legacy path constants and adding a focused API for path resolution and directory creation.
|
||||||
|
- Remove an unused import (paths) in contentscanner, cleaning up imports.
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.2.4 - fix(deps)
|
||||||
|
bump @push.rocks/smartproxy to ^25.5.0
|
||||||
|
|
||||||
|
- Updated @push.rocks/smartproxy from ^25.4.0 to ^25.5.0 in package.json
|
||||||
|
|
||||||
|
## 2026-02-16 - 6.2.3 - fix(dcrouter)
|
||||||
|
persist proxy certificate validity dates and improve certificate status initialization
|
||||||
|
|
||||||
|
- Bump @push.rocks/smartacme dependency from ^9.0.0 to ^9.1.3
|
||||||
|
- Store validFrom and validUntil alongside proxy cert entries (/proxy-certs) when saving, extracting values by parsing PEM where possible
|
||||||
|
- Use stored cert entries (domain, publicKey, validUntil, validFrom) to populate certificateStatusMap at startup
|
||||||
|
- Fallback to SmartAcme /certs/ metadata and finally to parsing X.509 from stored PEM to determine expiry/issuedAt when initializing status
|
||||||
|
- Update opsserver certificate handler to parse publicKey PEM from cert-store and set expiry/issuedAt and issuer accordingly
|
||||||
|
- Adjust variable names and logging to reflect stored cert entry usage
|
||||||
|
|
||||||
## 2026-02-16 - 6.2.2 - fix(certs)
|
## 2026-02-16 - 6.2.2 - fix(certs)
|
||||||
Populate certificate status for cert-store-loaded certificates after SmartProxy startup and check proxy-certs in opsserver certificate handler
|
Populate certificate status for cert-store-loaded certificates after SmartProxy startup and check proxy-certs in opsserver certificate handler
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "6.2.2",
|
"version": "6.4.5",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
"@api.global/typedserver": "^8.3.0",
|
"@api.global/typedserver": "^8.3.0",
|
||||||
"@api.global/typedsocket": "^4.1.0",
|
"@api.global/typedsocket": "^4.1.0",
|
||||||
"@apiclient.xyz/cloudflare": "^7.1.0",
|
"@apiclient.xyz/cloudflare": "^7.1.0",
|
||||||
"@design.estate/dees-catalog": "^3.42.0",
|
"@design.estate/dees-catalog": "^3.42.2",
|
||||||
"@design.estate/dees-element": "^2.1.6",
|
"@design.estate/dees-element": "^2.1.6",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/qenv": "^6.1.3",
|
"@push.rocks/qenv": "^6.1.3",
|
||||||
"@push.rocks/smartacme": "^9.0.0",
|
"@push.rocks/smartacme": "^9.1.3",
|
||||||
"@push.rocks/smartdata": "^7.0.15",
|
"@push.rocks/smartdata": "^7.0.15",
|
||||||
"@push.rocks/smartdns": "^7.8.1",
|
"@push.rocks/smartdns": "^7.8.1",
|
||||||
"@push.rocks/smartfile": "^13.1.2",
|
"@push.rocks/smartfile": "^13.1.2",
|
||||||
@@ -49,13 +49,14 @@
|
|||||||
"@push.rocks/smartnetwork": "^4.4.0",
|
"@push.rocks/smartnetwork": "^4.4.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartproxy": "^25.4.0",
|
"@push.rocks/smartproxy": "^25.7.3",
|
||||||
"@push.rocks/smartradius": "^1.1.1",
|
"@push.rocks/smartradius": "^1.1.1",
|
||||||
"@push.rocks/smartrequest": "^5.0.1",
|
"@push.rocks/smartrequest": "^5.0.1",
|
||||||
"@push.rocks/smartrx": "^3.0.10",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
"@push.rocks/smartstate": "^2.0.30",
|
"@push.rocks/smartstate": "^2.0.30",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@serve.zone/interfaces": "^5.3.0",
|
"@serve.zone/interfaces": "^5.3.0",
|
||||||
|
"@serve.zone/remoteingress": "^3.0.2",
|
||||||
"@tsclass/tsclass": "^9.3.0",
|
"@tsclass/tsclass": "^9.3.0",
|
||||||
"lru-cache": "^11.2.6",
|
"lru-cache": "^11.2.6",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
|
|||||||
214
pnpm-lock.yaml
generated
214
pnpm-lock.yaml
generated
@@ -24,8 +24,8 @@ importers:
|
|||||||
specifier: ^7.1.0
|
specifier: ^7.1.0
|
||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
'@design.estate/dees-catalog':
|
'@design.estate/dees-catalog':
|
||||||
specifier: ^3.42.0
|
specifier: ^3.42.2
|
||||||
version: 3.42.0(@tiptap/pm@2.27.2)
|
version: 3.42.2(@tiptap/pm@2.27.2)
|
||||||
'@design.estate/dees-element':
|
'@design.estate/dees-element':
|
||||||
specifier: ^2.1.6
|
specifier: ^2.1.6
|
||||||
version: 2.1.6
|
version: 2.1.6
|
||||||
@@ -36,8 +36,8 @@ importers:
|
|||||||
specifier: ^6.1.3
|
specifier: ^6.1.3
|
||||||
version: 6.1.3
|
version: 6.1.3
|
||||||
'@push.rocks/smartacme':
|
'@push.rocks/smartacme':
|
||||||
specifier: ^9.0.0
|
specifier: ^9.1.3
|
||||||
version: 9.1.2(socks@2.8.7)
|
version: 9.1.3(socks@2.8.7)
|
||||||
'@push.rocks/smartdata':
|
'@push.rocks/smartdata':
|
||||||
specifier: ^7.0.15
|
specifier: ^7.0.15
|
||||||
version: 7.0.15(socks@2.8.7)
|
version: 7.0.15(socks@2.8.7)
|
||||||
@@ -75,8 +75,8 @@ importers:
|
|||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
'@push.rocks/smartproxy':
|
'@push.rocks/smartproxy':
|
||||||
specifier: ^25.4.0
|
specifier: ^25.7.3
|
||||||
version: 25.4.0
|
version: 25.7.3
|
||||||
'@push.rocks/smartradius':
|
'@push.rocks/smartradius':
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
@@ -95,6 +95,9 @@ importers:
|
|||||||
'@serve.zone/interfaces':
|
'@serve.zone/interfaces':
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
|
'@serve.zone/remoteingress':
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
version: 9.3.0
|
version: 9.3.0
|
||||||
@@ -348,8 +351,8 @@ packages:
|
|||||||
'@configvault.io/interfaces@1.0.17':
|
'@configvault.io/interfaces@1.0.17':
|
||||||
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
|
||||||
|
|
||||||
'@design.estate/dees-catalog@3.42.0':
|
'@design.estate/dees-catalog@3.42.2':
|
||||||
resolution: {integrity: sha512-pArkafnrhRsHsSxKUMUM2YP5ei/AbcchPEKZY2PyHHAdXcNxyT3pE2Oh1FPcs1pqF2LpEgJRq8KFQbFhvhp8Nw==}
|
resolution: {integrity: sha512-e/d5XpIjuOmQIxHnBq81Uq+TyBHX92Ie1n7jEFBCYtxvi3+P2LU1sQ3VDrvLTpkwGxq7iyagu7BYWHYRtPLPmw==}
|
||||||
|
|
||||||
'@design.estate/dees-comms@1.0.30':
|
'@design.estate/dees-comms@1.0.30':
|
||||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||||
@@ -678,74 +681,74 @@ packages:
|
|||||||
'@mongodb-js/saslprep@1.4.6':
|
'@mongodb-js/saslprep@1.4.6':
|
||||||
resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==}
|
resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==}
|
||||||
|
|
||||||
'@napi-rs/canvas-android-arm64@0.1.91':
|
'@napi-rs/canvas-android-arm64@0.1.92':
|
||||||
resolution: {integrity: sha512-SLLzXXgSnfct4zy/BVAfweZQkYkPJsNsJ2e5DOE8DFEHC6PufyUrwb12yqeu2So2IOIDpWJJaDAxKY/xpy6MYQ==}
|
resolution: {integrity: sha512-rDOtq53ujfOuevD5taxAuIFALuf1QsQWZe1yS/N4MtT+tNiDBEdjufvQRPWZ11FubL2uwgP8ApYU3YOaNu1ZsQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-arm64@0.1.91':
|
'@napi-rs/canvas-darwin-arm64@0.1.92':
|
||||||
resolution: {integrity: sha512-bzdbCjIjw3iRuVFL+uxdSoMra/l09ydGNX9gsBxO/zg+5nlppscIpj6gg+nL6VNG85zwUarDleIrUJ+FWHvmuA==}
|
resolution: {integrity: sha512-4PT6GRGCr7yMRehp42x0LJb1V0IEy1cDZDDayv7eKbFUIGbPFkV7CRC9Bee5MPkjg1EB4ZPXXUyy3gjQm7mR8Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-x64@0.1.91':
|
'@napi-rs/canvas-darwin-x64@0.1.92':
|
||||||
resolution: {integrity: sha512-q3qpkpw0IsG9fAS/dmcGIhCVoNxj8ojbexZKWwz3HwxlEWsLncEQRl4arnxrwbpLc2nTNTyj4WwDn7QR5NDAaA==}
|
resolution: {integrity: sha512-5e/3ZapP7CqPtDcZPtmowCsjoyQwuNMMD7c0GKPtZQ8pgQhLkeq/3fmk0HqNSD1i227FyJN/9pDrhw/UMTkaWA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.91':
|
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.92':
|
||||||
resolution: {integrity: sha512-Io3g8wJZVhK8G+Fpg1363BE90pIPqg+ZbeehYNxPWDSzbgwU3xV0l8r/JBzODwC7XHi1RpFEk+xyUTMa2POj6w==}
|
resolution: {integrity: sha512-j6KaLL9iir68lwpzzY+aBGag1PZp3+gJE2mQ3ar4VJVmyLRVOh+1qsdNK1gfWoAVy5w6U7OEYFrLzN2vOFUSng==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.91':
|
'@napi-rs/canvas-linux-arm64-gnu@0.1.92':
|
||||||
resolution: {integrity: sha512-HBnto+0rxx1bQSl8bCWA9PyBKtlk2z/AI32r3cu4kcNO+M/5SD4b0v1MWBWZyqMQyxFjWgy3ECyDjDKMC6tY1A==}
|
resolution: {integrity: sha512-s3NlnJMHOSotUYVoTCoC1OcomaChFdKmZg0VsHFeIkeHbwX0uPHP4eCX1irjSfMykyvsGHTQDfBAtGYuqxCxhQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-musl@0.1.91':
|
'@napi-rs/canvas-linux-arm64-musl@0.1.92':
|
||||||
resolution: {integrity: sha512-/eJtVe2Xw9A86I4kwXpxxoNagdGclu12/NSMsfoL8q05QmeRCbfjhg1PJS7ENAuAvaiUiALGrbVfeY1KU1gztQ==}
|
resolution: {integrity: sha512-xV0GQnukYq5qY+ebkAwHjnP2OrSGBxS3vSi1zQNQj0bkXU6Ou+Tw7JjCM7pZcQ28MUyEBS1yKfo7rc7ip2IPFQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.91':
|
'@napi-rs/canvas-linux-riscv64-gnu@0.1.92':
|
||||||
resolution: {integrity: sha512-floNK9wQuRWevUhhXRcuis7h0zirdytVxPgkonWO+kQlbvxV7gEUHGUFQyq4n55UHYFwgck1SAfJ1HuXv/+ppQ==}
|
resolution: {integrity: sha512-+GKvIFbQ74eB/TopEdH6XIXcvOGcuKvCITLGXy7WLJAyNp3Kdn1ncjxg91ihatBaPR+t63QOE99yHuIWn3UQ9w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-gnu@0.1.91':
|
'@napi-rs/canvas-linux-x64-gnu@0.1.92':
|
||||||
resolution: {integrity: sha512-c3YDqBdf7KETuZy2AxsHFMsBBX1dWT43yFfWUq+j1IELdgesWtxf/6N7csi3VPf6VA3PmnT9EhMyb+M1wfGtqw==}
|
resolution: {integrity: sha512-tFd6MwbEhZ1g64iVY2asV+dOJC+GT3Yd6UH4G3Hp0/VHQ6qikB+nvXEULskFYZ0+wFqlGPtXjG1Jmv7sJy+3Ww==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-musl@0.1.91':
|
'@napi-rs/canvas-linux-x64-musl@0.1.92':
|
||||||
resolution: {integrity: sha512-RpZ3RPIwgEcNBHSHSX98adm+4VP8SMT5FN6250s5jQbWpX/XNUX5aLMfAVJS/YnDjS1QlsCgQxFOPU0aCCWgag==}
|
resolution: {integrity: sha512-uSuqeSveB/ZGd72VfNbHCSXO9sArpZTvznMVsb42nqPP7gBGEH6NJQ0+hmF+w24unEmxBhPYakP/Wiosm16KkA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.91':
|
'@napi-rs/canvas-win32-arm64-msvc@0.1.92':
|
||||||
resolution: {integrity: sha512-gF8MBp4X134AgVurxqlCdDA2qO0WaDdi9o6Sd5rWRVXRhWhYQ6wkdEzXNLIrmmros0Tsp2J0hQzx4ej/9O8trQ==}
|
resolution: {integrity: sha512-20SK5AU/OUNz9ZuoAPj5ekWai45EIBDh/XsdrVZ8le/pJVlhjFU3olbumSQUXRFn7lBRS+qwM8kA//uLaDx6iQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-x64-msvc@0.1.91':
|
'@napi-rs/canvas-win32-x64-msvc@0.1.92':
|
||||||
resolution: {integrity: sha512-++gtW9EV/neKI8TshD8WFxzBYALSPag2kFRahIJV+LYsyt5kBn21b1dBhEUDHf7O+wiZmuFCeUa7QKGHnYRZBA==}
|
resolution: {integrity: sha512-KEhyZLzq1MXCNlXybz4k25MJmHFp+uK1SIb8yJB0xfrQjz5aogAMhyseSzewo+XxAq3OAOdyKvfHGNzT3w1RPg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@napi-rs/canvas@0.1.91':
|
'@napi-rs/canvas@0.1.92':
|
||||||
resolution: {integrity: sha512-eeIe1GoB74P1B0Nkw6pV8BCQ3hfCfvyYr4BntzlCsnFXzVJiPMDnLeIx3gVB0xQMblHYnjK/0nCLvirEhOjr5g==}
|
resolution: {integrity: sha512-q7ZaUCJkEU5BeOdE7fBx1XWRd2T5Ady65nxq4brMf5L4cE1VV/ACq5w9Z5b/IVJs8CwSSIwc30nlthH0gFo4Ig==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.0.7':
|
'@napi-rs/wasm-runtime@1.0.7':
|
||||||
@@ -852,8 +855,8 @@ packages:
|
|||||||
'@push.rocks/qenv@6.1.3':
|
'@push.rocks/qenv@6.1.3':
|
||||||
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
|
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
|
||||||
|
|
||||||
'@push.rocks/smartacme@9.1.2':
|
'@push.rocks/smartacme@9.1.3':
|
||||||
resolution: {integrity: sha512-pcYJ9iFwCV4KcRRrxU8VJBYTjgzVv1LnWqkFcEDJJvLdnxwxggpwMZZ+g/CCJlb7gOUkDuTPbfCX7deDvWeIoQ==}
|
resolution: {integrity: sha512-rxb4zGZQvcR7l8cb8SvLy+zkCgXKg8rO7b12zaE9ZBe5Q+khoInxscC0eKjmNZ7BOUFFDOxDKoQhgeqwHGOqZQ==}
|
||||||
|
|
||||||
'@push.rocks/smartarchive@4.2.4':
|
'@push.rocks/smartarchive@4.2.4':
|
||||||
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
|
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
|
||||||
@@ -1031,8 +1034,8 @@ packages:
|
|||||||
'@push.rocks/smartpromise@4.2.3':
|
'@push.rocks/smartpromise@4.2.3':
|
||||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@25.4.0':
|
'@push.rocks/smartproxy@25.7.3':
|
||||||
resolution: {integrity: sha512-aU7ySk/2llRs6hcIGrl4gjuXsJOmLuVv952ys4H1yZyZSdPx7G8m5gJl6RxB5Rp8GzM2YaKUvCp00apcGcnEfw==}
|
resolution: {integrity: sha512-9b5dwsLAhuDqnJptGBum4qBHlZwZPqPG3CJKxAwE3uFKjCmcE8qGDwodI0CjrQ7KW2PJ1BMq/Lk4ghs3Da6PWw==}
|
||||||
|
|
||||||
'@push.rocks/smartpuppeteer@2.0.5':
|
'@push.rocks/smartpuppeteer@2.0.5':
|
||||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||||
@@ -1337,6 +1340,9 @@ packages:
|
|||||||
'@serve.zone/interfaces@5.3.0':
|
'@serve.zone/interfaces@5.3.0':
|
||||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||||
|
|
||||||
|
'@serve.zone/remoteingress@3.0.2':
|
||||||
|
resolution: {integrity: sha512-FnwNVO0Dn9xuNv0t81u6pjCizSeCyMjkRKm6wN5qycCdGFoJmFbBamHqV01KtK1KcgDTd7LX+PowSqKReNrBGw==}
|
||||||
|
|
||||||
'@sindresorhus/is@5.6.0':
|
'@sindresorhus/is@5.6.0':
|
||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
@@ -1560,31 +1566,6 @@ packages:
|
|||||||
'@socket.io/component-emitter@3.1.2':
|
'@socket.io/component-emitter@3.1.2':
|
||||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
|
|
||||||
'@svgdotjs/svg.draggable.js@3.0.6':
|
|
||||||
resolution: {integrity: sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==}
|
|
||||||
peerDependencies:
|
|
||||||
'@svgdotjs/svg.js': ^3.2.4
|
|
||||||
|
|
||||||
'@svgdotjs/svg.filter.js@3.0.9':
|
|
||||||
resolution: {integrity: sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==}
|
|
||||||
engines: {node: '>= 0.8.0'}
|
|
||||||
|
|
||||||
'@svgdotjs/svg.js@3.2.5':
|
|
||||||
resolution: {integrity: sha512-/VNHWYhNu+BS7ktbYoVGrCmsXDh+chFMaONMwGNdIBcFHrWqk2jY8fNyr3DLdtQUIalvkPfM554ZSFa3dm3nxQ==}
|
|
||||||
|
|
||||||
'@svgdotjs/svg.resize.js@2.0.5':
|
|
||||||
resolution: {integrity: sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==}
|
|
||||||
engines: {node: '>= 14.18'}
|
|
||||||
peerDependencies:
|
|
||||||
'@svgdotjs/svg.js': ^3.2.4
|
|
||||||
'@svgdotjs/svg.select.js': ^4.0.1
|
|
||||||
|
|
||||||
'@svgdotjs/svg.select.js@4.0.3':
|
|
||||||
resolution: {integrity: sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==}
|
|
||||||
engines: {node: '>= 14.18'}
|
|
||||||
peerDependencies:
|
|
||||||
'@svgdotjs/svg.js': ^3.2.4
|
|
||||||
|
|
||||||
'@szmarczak/http-timer@5.0.1':
|
'@szmarczak/http-timer@5.0.1':
|
||||||
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
@@ -1985,8 +1966,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
apexcharts@5.3.6:
|
apexcharts@5.5.0:
|
||||||
resolution: {integrity: sha512-sVEPw+J0Gp0IHQabKu8cfdsxlfME0e36Wid7RIaPclGM2OUt+O7O4+6mfAmTUYhy5bDk8cNHzEhPfVtLCIXEJA==}
|
resolution: {integrity: sha512-r0GzBUmIAihVDHiPTWrKzd2I+T2Dw+oZTDBRJeBExUuCyqEaCe2pAMEKZnTbJQXyDAhCBzPgkM2SeeKQuW4Ddw==}
|
||||||
|
|
||||||
argparse@1.0.10:
|
argparse@1.0.10:
|
||||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||||
@@ -3038,8 +3019,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
lucide@0.563.0:
|
lucide@0.564.0:
|
||||||
resolution: {integrity: sha512-2zBzDJ5n2Plj3d0ksj6h9TWPOSiKu9gtxJxnBAye11X/8gfWied6IYJn6ADYBp1NPoJmgpyOYP3wMrVx69+2AA==}
|
resolution: {integrity: sha512-FasyXKHWon773WIl3HeCQpd5xS6E0aLjqxiQStlHNKktni+HDncc1sqY+6vRUbCfmDsIaKQz43EEQLAUDLZO0g==}
|
||||||
|
|
||||||
mailparser@3.9.3:
|
mailparser@3.9.3:
|
||||||
resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==}
|
resolution: {integrity: sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ==}
|
||||||
@@ -3602,8 +3583,8 @@ packages:
|
|||||||
property-information@7.1.0:
|
property-information@7.1.0:
|
||||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||||
|
|
||||||
prosemirror-changeset@2.3.1:
|
prosemirror-changeset@2.4.0:
|
||||||
resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
|
resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==}
|
||||||
|
|
||||||
prosemirror-collab@1.3.1:
|
prosemirror-collab@1.3.1:
|
||||||
resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
|
resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
|
||||||
@@ -4266,11 +4247,13 @@ packages:
|
|||||||
|
|
||||||
xterm-addon-fit@0.8.0:
|
xterm-addon-fit@0.8.0:
|
||||||
resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==}
|
resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==}
|
||||||
|
deprecated: This package is now deprecated. Move to @xterm/addon-fit instead.
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
xterm: ^5.0.0
|
xterm: ^5.0.0
|
||||||
|
|
||||||
xterm@5.3.0:
|
xterm@5.3.0:
|
||||||
resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==}
|
resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==}
|
||||||
|
deprecated: This package is now deprecated. Move to @xterm/xterm instead.
|
||||||
|
|
||||||
y18n@5.0.8:
|
y18n@5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
@@ -4382,7 +4365,7 @@ snapshots:
|
|||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@2.0.1)
|
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@2.0.1)
|
||||||
'@cloudflare/workers-types': 4.20260210.0
|
'@cloudflare/workers-types': 4.20260210.0
|
||||||
'@design.estate/dees-catalog': 3.42.0(@tiptap/pm@2.27.2)
|
'@design.estate/dees-catalog': 3.42.2(@tiptap/pm@2.27.2)
|
||||||
'@design.estate/dees-comms': 1.0.30
|
'@design.estate/dees-comms': 1.0.30
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
@@ -4980,7 +4963,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
|
|
||||||
'@design.estate/dees-catalog@3.42.0(@tiptap/pm@2.27.2)':
|
'@design.estate/dees-catalog@3.42.2(@tiptap/pm@2.27.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@design.estate/dees-domtools': 2.3.8
|
'@design.estate/dees-domtools': 2.3.8
|
||||||
'@design.estate/dees-element': 2.1.6
|
'@design.estate/dees-element': 2.1.6
|
||||||
@@ -5000,10 +4983,10 @@ snapshots:
|
|||||||
'@tiptap/extension-underline': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))
|
'@tiptap/extension-underline': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2))
|
||||||
'@tiptap/starter-kit': 2.27.2
|
'@tiptap/starter-kit': 2.27.2
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
apexcharts: 5.3.6
|
apexcharts: 5.5.0
|
||||||
highlight.js: 11.11.1
|
highlight.js: 11.11.1
|
||||||
ibantools: 4.5.1
|
ibantools: 4.5.1
|
||||||
lucide: 0.563.0
|
lucide: 0.564.0
|
||||||
monaco-editor: 0.55.1
|
monaco-editor: 0.55.1
|
||||||
pdfjs-dist: 4.10.38
|
pdfjs-dist: 4.10.38
|
||||||
xterm: 5.3.0
|
xterm: 5.3.0
|
||||||
@@ -5489,52 +5472,52 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sparse-bitfield: 3.0.3
|
sparse-bitfield: 3.0.3
|
||||||
|
|
||||||
'@napi-rs/canvas-android-arm64@0.1.91':
|
'@napi-rs/canvas-android-arm64@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-arm64@0.1.91':
|
'@napi-rs/canvas-darwin-arm64@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-darwin-x64@0.1.91':
|
'@napi-rs/canvas-darwin-x64@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.91':
|
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.91':
|
'@napi-rs/canvas-linux-arm64-gnu@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-arm64-musl@0.1.91':
|
'@napi-rs/canvas-linux-arm64-musl@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.91':
|
'@napi-rs/canvas-linux-riscv64-gnu@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-gnu@0.1.91':
|
'@napi-rs/canvas-linux-x64-gnu@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-linux-x64-musl@0.1.91':
|
'@napi-rs/canvas-linux-x64-musl@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.91':
|
'@napi-rs/canvas-win32-arm64-msvc@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas-win32-x64-msvc@0.1.91':
|
'@napi-rs/canvas-win32-x64-msvc@0.1.92':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/canvas@0.1.91':
|
'@napi-rs/canvas@0.1.92':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@napi-rs/canvas-android-arm64': 0.1.91
|
'@napi-rs/canvas-android-arm64': 0.1.92
|
||||||
'@napi-rs/canvas-darwin-arm64': 0.1.91
|
'@napi-rs/canvas-darwin-arm64': 0.1.92
|
||||||
'@napi-rs/canvas-darwin-x64': 0.1.91
|
'@napi-rs/canvas-darwin-x64': 0.1.92
|
||||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.91
|
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.92
|
||||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.91
|
'@napi-rs/canvas-linux-arm64-gnu': 0.1.92
|
||||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.91
|
'@napi-rs/canvas-linux-arm64-musl': 0.1.92
|
||||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.91
|
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.92
|
||||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.91
|
'@napi-rs/canvas-linux-x64-gnu': 0.1.92
|
||||||
'@napi-rs/canvas-linux-x64-musl': 0.1.91
|
'@napi-rs/canvas-linux-x64-musl': 0.1.92
|
||||||
'@napi-rs/canvas-win32-arm64-msvc': 0.1.91
|
'@napi-rs/canvas-win32-arm64-msvc': 0.1.92
|
||||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.91
|
'@napi-rs/canvas-win32-x64-msvc': 0.1.92
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.0.7':
|
'@napi-rs/wasm-runtime@1.0.7':
|
||||||
@@ -5782,7 +5765,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
|
||||||
'@push.rocks/smartacme@9.1.2(socks@2.8.7)':
|
'@push.rocks/smartacme@9.1.3(socks@2.8.7)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@apiclient.xyz/cloudflare': 7.1.0
|
'@apiclient.xyz/cloudflare': 7.1.0
|
||||||
'@peculiar/x509': 1.14.3
|
'@peculiar/x509': 1.14.3
|
||||||
@@ -6369,7 +6352,7 @@ snapshots:
|
|||||||
|
|
||||||
'@push.rocks/smartpromise@4.2.3': {}
|
'@push.rocks/smartpromise@4.2.3': {}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@25.4.0':
|
'@push.rocks/smartproxy@25.7.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
'@push.rocks/smartlog': 3.1.11
|
'@push.rocks/smartlog': 3.1.11
|
||||||
@@ -6847,6 +6830,11 @@ snapshots:
|
|||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
|
||||||
|
'@serve.zone/remoteingress@3.0.2':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/qenv': 6.1.3
|
||||||
|
'@push.rocks/smartrust': 1.2.1
|
||||||
|
|
||||||
'@sindresorhus/is@5.6.0': {}
|
'@sindresorhus/is@5.6.0': {}
|
||||||
|
|
||||||
'@smithy/abort-controller@4.2.8':
|
'@smithy/abort-controller@4.2.8':
|
||||||
@@ -7189,25 +7177,6 @@ snapshots:
|
|||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2': {}
|
'@socket.io/component-emitter@3.1.2': {}
|
||||||
|
|
||||||
'@svgdotjs/svg.draggable.js@3.0.6(@svgdotjs/svg.js@3.2.5)':
|
|
||||||
dependencies:
|
|
||||||
'@svgdotjs/svg.js': 3.2.5
|
|
||||||
|
|
||||||
'@svgdotjs/svg.filter.js@3.0.9':
|
|
||||||
dependencies:
|
|
||||||
'@svgdotjs/svg.js': 3.2.5
|
|
||||||
|
|
||||||
'@svgdotjs/svg.js@3.2.5': {}
|
|
||||||
|
|
||||||
'@svgdotjs/svg.resize.js@2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5))':
|
|
||||||
dependencies:
|
|
||||||
'@svgdotjs/svg.js': 3.2.5
|
|
||||||
'@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5)
|
|
||||||
|
|
||||||
'@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5)':
|
|
||||||
dependencies:
|
|
||||||
'@svgdotjs/svg.js': 3.2.5
|
|
||||||
|
|
||||||
'@szmarczak/http-timer@5.0.1':
|
'@szmarczak/http-timer@5.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
defer-to-connect: 2.0.1
|
defer-to-connect: 2.0.1
|
||||||
@@ -7323,7 +7292,7 @@ snapshots:
|
|||||||
|
|
||||||
'@tiptap/pm@2.27.2':
|
'@tiptap/pm@2.27.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-changeset: 2.3.1
|
prosemirror-changeset: 2.4.0
|
||||||
prosemirror-collab: 1.3.1
|
prosemirror-collab: 1.3.1
|
||||||
prosemirror-commands: 1.7.1
|
prosemirror-commands: 1.7.1
|
||||||
prosemirror-dropcursor: 1.8.2
|
prosemirror-dropcursor: 1.8.2
|
||||||
@@ -7636,13 +7605,8 @@ snapshots:
|
|||||||
|
|
||||||
ansi-styles@6.2.3: {}
|
ansi-styles@6.2.3: {}
|
||||||
|
|
||||||
apexcharts@5.3.6:
|
apexcharts@5.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@svgdotjs/svg.draggable.js': 3.0.6(@svgdotjs/svg.js@3.2.5)
|
|
||||||
'@svgdotjs/svg.filter.js': 3.0.9
|
|
||||||
'@svgdotjs/svg.js': 3.2.5
|
|
||||||
'@svgdotjs/svg.resize.js': 2.0.5(@svgdotjs/svg.js@3.2.5)(@svgdotjs/svg.select.js@4.0.3(@svgdotjs/svg.js@3.2.5))
|
|
||||||
'@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.5)
|
|
||||||
'@yr/monotone-cubic-spline': 1.0.3
|
'@yr/monotone-cubic-spline': 1.0.3
|
||||||
|
|
||||||
argparse@1.0.10:
|
argparse@1.0.10:
|
||||||
@@ -8798,7 +8762,7 @@ snapshots:
|
|||||||
|
|
||||||
lru-cache@7.18.3: {}
|
lru-cache@7.18.3: {}
|
||||||
|
|
||||||
lucide@0.563.0: {}
|
lucide@0.564.0: {}
|
||||||
|
|
||||||
mailparser@3.9.3:
|
mailparser@3.9.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9463,7 +9427,7 @@ snapshots:
|
|||||||
|
|
||||||
pdfjs-dist@4.10.38:
|
pdfjs-dist@4.10.38:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@napi-rs/canvas': 0.1.91
|
'@napi-rs/canvas': 0.1.92
|
||||||
|
|
||||||
peberminta@0.9.0: {}
|
peberminta@0.9.0: {}
|
||||||
|
|
||||||
@@ -9502,7 +9466,7 @@ snapshots:
|
|||||||
|
|
||||||
property-information@7.1.0: {}
|
property-information@7.1.0: {}
|
||||||
|
|
||||||
prosemirror-changeset@2.3.1:
|
prosemirror-changeset@2.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-transform: 1.11.0
|
prosemirror-transform: 1.11.0
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '6.2.2',
|
version: '6.4.5',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ import { CacheDb, CacheCleaner, type ICacheDbOptions } from './cache/index.js';
|
|||||||
import { OpsServer } from './opsserver/index.js';
|
import { OpsServer } from './opsserver/index.js';
|
||||||
import { MetricsManager } from './monitoring/index.js';
|
import { MetricsManager } from './monitoring/index.js';
|
||||||
import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
|
import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
|
||||||
|
import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
|
||||||
|
|
||||||
export interface IDcRouterOptions {
|
export interface IDcRouterOptions {
|
||||||
|
/** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
|
||||||
|
baseDir?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct SmartProxy configuration - gives full control over HTTP/HTTPS and TCP/SNI traffic
|
* Direct SmartProxy configuration - gives full control over HTTP/HTTPS and TCP/SNI traffic
|
||||||
* This is the preferred way to configure HTTP/HTTPS and general TCP/SNI traffic
|
* This is the preferred way to configure HTTP/HTTPS and general TCP/SNI traffic
|
||||||
@@ -152,6 +156,22 @@ export interface IDcRouterOptions {
|
|||||||
* Enables MAC Authentication Bypass (MAB) and VLAN assignment
|
* Enables MAC Authentication Bypass (MAB) and VLAN assignment
|
||||||
*/
|
*/
|
||||||
radiusConfig?: IRadiusServerConfig;
|
radiusConfig?: IRadiusServerConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remote Ingress configuration for edge tunnel nodes
|
||||||
|
* Enables edge nodes to accept incoming connections and tunnel them to this DcRouter
|
||||||
|
*/
|
||||||
|
remoteIngressConfig?: {
|
||||||
|
/** Enable remote ingress hub (default: false) */
|
||||||
|
enabled?: boolean;
|
||||||
|
/** Port for tunnel connections from edge nodes (default: 8443) */
|
||||||
|
tunnelPort?: number;
|
||||||
|
/** TLS configuration for the tunnel server */
|
||||||
|
tls?: {
|
||||||
|
certPath?: string;
|
||||||
|
keyPath?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,6 +190,7 @@ export interface PortProxyRuleContext {
|
|||||||
|
|
||||||
export class DcRouter {
|
export class DcRouter {
|
||||||
public options: IDcRouterOptions;
|
public options: IDcRouterOptions;
|
||||||
|
public resolvedPaths: ReturnType<typeof paths.resolvePaths>;
|
||||||
|
|
||||||
// Core services
|
// Core services
|
||||||
public smartProxy?: plugins.smartproxy.SmartProxy;
|
public smartProxy?: plugins.smartproxy.SmartProxy;
|
||||||
@@ -185,6 +206,10 @@ export class DcRouter {
|
|||||||
public cacheDb?: CacheDb;
|
public cacheDb?: CacheDb;
|
||||||
public cacheCleaner?: CacheCleaner;
|
public cacheCleaner?: CacheCleaner;
|
||||||
|
|
||||||
|
// Remote Ingress
|
||||||
|
public remoteIngressManager?: RemoteIngressManager;
|
||||||
|
public tunnelManager?: TunnelManager;
|
||||||
|
|
||||||
// Certificate status tracking from SmartProxy events (keyed by domain)
|
// Certificate status tracking from SmartProxy events (keyed by domain)
|
||||||
public certificateStatusMap = new Map<string, {
|
public certificateStatusMap = new Map<string, {
|
||||||
status: 'valid' | 'failed';
|
status: 'valid' | 'failed';
|
||||||
@@ -210,10 +235,13 @@ export class DcRouter {
|
|||||||
...optionsArg
|
...optionsArg
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Resolve all data paths from baseDir
|
||||||
|
this.resolvedPaths = paths.resolvePaths(this.options.baseDir);
|
||||||
|
|
||||||
// Default storage to filesystem if not configured
|
// Default storage to filesystem if not configured
|
||||||
if (!this.options.storage) {
|
if (!this.options.storage) {
|
||||||
this.options.storage = {
|
this.options.storage = {
|
||||||
fsPath: plugins.path.join(paths.dcrouterHomeDir, 'storage'),
|
fsPath: this.resolvedPaths.defaultStoragePath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +287,11 @@ export class DcRouter {
|
|||||||
await this.setupRadiusServer();
|
await this.setupRadiusServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up Remote Ingress hub if configured
|
||||||
|
if (this.options.remoteIngressConfig?.enabled) {
|
||||||
|
await this.setupRemoteIngress();
|
||||||
|
}
|
||||||
|
|
||||||
this.logStartupSummary();
|
this.logStartupSummary();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error starting DcRouter:', error);
|
console.error('❌ Error starting DcRouter:', error);
|
||||||
@@ -345,6 +378,16 @@ export class DcRouter {
|
|||||||
console.log(` └─ Accounting: ${this.options.radiusConfig.accounting?.enabled ? 'Enabled' : 'Disabled'}`);
|
console.log(` └─ Accounting: ${this.options.radiusConfig.accounting?.enabled ? 'Enabled' : 'Disabled'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remote Ingress summary
|
||||||
|
if (this.tunnelManager && this.options.remoteIngressConfig?.enabled) {
|
||||||
|
console.log('\n🌐 Remote Ingress:');
|
||||||
|
console.log(` ├─ Tunnel Port: ${this.options.remoteIngressConfig.tunnelPort || 8443}`);
|
||||||
|
const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0;
|
||||||
|
const connectedCount = this.tunnelManager.getConnectedCount();
|
||||||
|
console.log(` ├─ Registered Edges: ${edgeCount}`);
|
||||||
|
console.log(` └─ Connected Edges: ${connectedCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Storage summary
|
// Storage summary
|
||||||
if (this.storageManager && this.options.storage) {
|
if (this.storageManager && this.options.storage) {
|
||||||
console.log('\n💾 Storage:');
|
console.log('\n💾 Storage:');
|
||||||
@@ -372,7 +415,7 @@ export class DcRouter {
|
|||||||
|
|
||||||
// Initialize CacheDb singleton
|
// Initialize CacheDb singleton
|
||||||
this.cacheDb = CacheDb.getInstance({
|
this.cacheDb = CacheDb.getInstance({
|
||||||
storagePath: cacheConfig.storagePath || paths.defaultTsmDbPath,
|
storagePath: cacheConfig.storagePath || this.resolvedPaths.defaultTsmDbPath,
|
||||||
dbName: cacheConfig.dbName || 'dcrouter',
|
dbName: cacheConfig.dbName || 'dcrouter',
|
||||||
debug: false,
|
debug: false,
|
||||||
});
|
});
|
||||||
@@ -445,8 +488,8 @@ export class DcRouter {
|
|||||||
if (routes.length > 0 || this.options.smartProxyConfig) {
|
if (routes.length > 0 || this.options.smartProxyConfig) {
|
||||||
console.log('Setting up SmartProxy with combined configuration');
|
console.log('Setting up SmartProxy with combined configuration');
|
||||||
|
|
||||||
// Track domains loaded from cert store so we can populate certificateStatusMap after start
|
// Track cert entries loaded from cert store so we can populate certificateStatusMap after start
|
||||||
const loadedCertDomains: string[] = [];
|
const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = [];
|
||||||
|
|
||||||
// Create SmartProxy configuration
|
// Create SmartProxy configuration
|
||||||
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
|
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
|
||||||
@@ -461,14 +504,21 @@ export class DcRouter {
|
|||||||
const data = await this.storageManager.getJSON(key);
|
const data = await this.storageManager.getJSON(key);
|
||||||
if (data) {
|
if (data) {
|
||||||
certs.push(data);
|
certs.push(data);
|
||||||
loadedCertDomains.push(data.domain);
|
loadedCertEntries.push({ domain: data.domain, publicKey: data.publicKey, validUntil: data.validUntil, validFrom: data.validFrom });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return certs;
|
return certs;
|
||||||
},
|
},
|
||||||
save: async (domain: string, publicKey: string, privateKey: string, ca?: string) => {
|
save: async (domain: string, publicKey: string, privateKey: string, ca?: string) => {
|
||||||
|
let validUntil: number | undefined;
|
||||||
|
let validFrom: number | undefined;
|
||||||
|
try {
|
||||||
|
const x509 = new plugins.crypto.X509Certificate(publicKey);
|
||||||
|
validUntil = new Date(x509.validTo).getTime();
|
||||||
|
validFrom = new Date(x509.validFrom).getTime();
|
||||||
|
} catch { /* PEM parsing failed */ }
|
||||||
await this.storageManager.setJSON(`/proxy-certs/${domain}`, {
|
await this.storageManager.setJSON(`/proxy-certs/${domain}`, {
|
||||||
domain, publicKey, privateKey, ca,
|
domain, publicKey, privateKey, ca, validUntil, validFrom,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
remove: async (domain: string) => {
|
remove: async (domain: string) => {
|
||||||
@@ -587,22 +637,46 @@ export class DcRouter {
|
|||||||
console.log('[DcRouter] SmartProxy started successfully');
|
console.log('[DcRouter] SmartProxy started successfully');
|
||||||
|
|
||||||
// Populate certificateStatusMap for certs loaded from store at startup
|
// Populate certificateStatusMap for certs loaded from store at startup
|
||||||
for (const domain of loadedCertDomains) {
|
for (const entry of loadedCertEntries) {
|
||||||
if (!this.certificateStatusMap.has(domain)) {
|
if (!this.certificateStatusMap.has(entry.domain)) {
|
||||||
const routeNames = this.findRouteNamesForDomain(domain);
|
const routeNames = this.findRouteNamesForDomain(entry.domain);
|
||||||
let expiryDate: string | undefined;
|
let expiryDate: string | undefined;
|
||||||
let issuedAt: string | undefined;
|
let issuedAt: string | undefined;
|
||||||
|
|
||||||
|
// Use validUntil/validFrom from stored proxy-certs data if available
|
||||||
|
if (entry.validUntil) {
|
||||||
|
expiryDate = new Date(entry.validUntil).toISOString();
|
||||||
|
}
|
||||||
|
if (entry.validFrom) {
|
||||||
|
issuedAt = new Date(entry.validFrom).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try SmartAcme /certs/ metadata as secondary source
|
||||||
|
if (!expiryDate) {
|
||||||
try {
|
try {
|
||||||
const cleanDomain = domain.replace(/^\*\.?/, '');
|
const cleanDomain = entry.domain.replace(/^\*\.?/, '');
|
||||||
const certMeta = await this.storageManager.getJSON(`/certs/${cleanDomain}`);
|
const certMeta = await this.storageManager.getJSON(`/certs/${cleanDomain}`);
|
||||||
if (certMeta?.validUntil) {
|
if (certMeta?.validUntil) {
|
||||||
expiryDate = new Date(certMeta.validUntil).toISOString();
|
expiryDate = new Date(certMeta.validUntil).toISOString();
|
||||||
}
|
}
|
||||||
if (certMeta?.created) {
|
if (certMeta?.created && !issuedAt) {
|
||||||
issuedAt = new Date(certMeta.created).toISOString();
|
issuedAt = new Date(certMeta.created).toISOString();
|
||||||
}
|
}
|
||||||
} catch { /* no metadata available */ }
|
} catch { /* no metadata available */ }
|
||||||
this.certificateStatusMap.set(domain, {
|
}
|
||||||
|
|
||||||
|
// Fallback: parse X509 from PEM to get expiry
|
||||||
|
if (!expiryDate && entry.publicKey) {
|
||||||
|
try {
|
||||||
|
const x509 = new plugins.crypto.X509Certificate(entry.publicKey);
|
||||||
|
expiryDate = new Date(x509.validTo).toISOString();
|
||||||
|
if (!issuedAt) {
|
||||||
|
issuedAt = new Date(x509.validFrom).toISOString();
|
||||||
|
}
|
||||||
|
} catch { /* PEM parsing failed */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
this.certificateStatusMap.set(entry.domain, {
|
||||||
status: 'valid',
|
status: 'valid',
|
||||||
routeNames,
|
routeNames,
|
||||||
expiryDate,
|
expiryDate,
|
||||||
@@ -611,8 +685,8 @@ export class DcRouter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (loadedCertDomains.length > 0) {
|
if (loadedCertEntries.length > 0) {
|
||||||
console.log(`[DcRouter] Populated certificate status for ${loadedCertDomains.length} store-loaded domain(s)`);
|
console.log(`[DcRouter] Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`SmartProxy started with ${routes.length} routes`);
|
console.log(`SmartProxy started with ${routes.length} routes`);
|
||||||
@@ -848,6 +922,11 @@ export class DcRouter {
|
|||||||
// Stop RADIUS server if running
|
// Stop RADIUS server if running
|
||||||
this.radiusServer ?
|
this.radiusServer ?
|
||||||
this.radiusServer.stop().catch(err => console.error('Error stopping RADIUS server:', err)) :
|
this.radiusServer.stop().catch(err => console.error('Error stopping RADIUS server:', err)) :
|
||||||
|
Promise.resolve(),
|
||||||
|
|
||||||
|
// Stop Remote Ingress tunnel manager if running
|
||||||
|
this.tunnelManager ?
|
||||||
|
this.tunnelManager.stop().catch(err => console.error('Error stopping TunnelManager:', err)) :
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -1275,7 +1354,7 @@ export class DcRouter {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure paths are imported
|
// Ensure paths are imported
|
||||||
const dnsDir = paths.dnsRecordsDir;
|
const dnsDir = this.resolvedPaths.dnsRecordsDir;
|
||||||
|
|
||||||
// Check if directory exists
|
// Check if directory exists
|
||||||
if (!plugins.fs.existsSync(dnsDir)) {
|
if (!plugins.fs.existsSync(dnsDir)) {
|
||||||
@@ -1339,7 +1418,7 @@ export class DcRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure necessary directories exist
|
// Ensure necessary directories exist
|
||||||
paths.ensureDirectories();
|
paths.ensureDataDirectories(this.resolvedPaths);
|
||||||
|
|
||||||
// Generate DKIM keys for each email domain
|
// Generate DKIM keys for each email domain
|
||||||
for (const domainConfig of this.options.emailConfig.domains) {
|
for (const domainConfig of this.options.emailConfig.domains) {
|
||||||
@@ -1494,6 +1573,31 @@ export class DcRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up Remote Ingress hub for edge tunnel connections
|
||||||
|
*/
|
||||||
|
private async setupRemoteIngress(): Promise<void> {
|
||||||
|
if (!this.options.remoteIngressConfig?.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('info', 'Setting up Remote Ingress hub...');
|
||||||
|
|
||||||
|
// Initialize the edge registration manager
|
||||||
|
this.remoteIngressManager = new RemoteIngressManager(this.storageManager);
|
||||||
|
await this.remoteIngressManager.initialize();
|
||||||
|
|
||||||
|
// Create and start the tunnel manager
|
||||||
|
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
||||||
|
tunnelPort: this.options.remoteIngressConfig.tunnelPort ?? 8443,
|
||||||
|
targetHost: '127.0.0.1',
|
||||||
|
});
|
||||||
|
await this.tunnelManager.start();
|
||||||
|
|
||||||
|
const edgeCount = this.remoteIngressManager.getAllEdges().length;
|
||||||
|
logger.log('info', `Remote Ingress hub started on port ${this.options.remoteIngressConfig.tunnelPort || 8443} with ${edgeCount} registered edge(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up RADIUS server for network authentication
|
* Set up RADIUS server for network authentication
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,4 +10,7 @@ export * from './classes.dcrouter.js';
|
|||||||
// RADIUS module
|
// RADIUS module
|
||||||
export * from './radius/index.js';
|
export * from './radius/index.js';
|
||||||
|
|
||||||
|
// Remote Ingress module
|
||||||
|
export * from './remoteingress/index.js';
|
||||||
|
|
||||||
export const runCli = async () => {};
|
export const runCli = async () => {};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export class OpsServer {
|
|||||||
private radiusHandler: handlers.RadiusHandler;
|
private radiusHandler: handlers.RadiusHandler;
|
||||||
private emailOpsHandler: handlers.EmailOpsHandler;
|
private emailOpsHandler: handlers.EmailOpsHandler;
|
||||||
private certificateHandler: handlers.CertificateHandler;
|
private certificateHandler: handlers.CertificateHandler;
|
||||||
|
private remoteIngressHandler: handlers.RemoteIngressHandler;
|
||||||
|
|
||||||
constructor(dcRouterRefArg: DcRouter) {
|
constructor(dcRouterRefArg: DcRouter) {
|
||||||
this.dcRouterRef = dcRouterRefArg;
|
this.dcRouterRef = dcRouterRefArg;
|
||||||
@@ -59,6 +60,7 @@ export class OpsServer {
|
|||||||
this.radiusHandler = new handlers.RadiusHandler(this);
|
this.radiusHandler = new handlers.RadiusHandler(this);
|
||||||
this.emailOpsHandler = new handlers.EmailOpsHandler(this);
|
this.emailOpsHandler = new handlers.EmailOpsHandler(this);
|
||||||
this.certificateHandler = new handlers.CertificateHandler(this);
|
this.certificateHandler = new handlers.CertificateHandler(this);
|
||||||
|
this.remoteIngressHandler = new handlers.RemoteIngressHandler(this);
|
||||||
|
|
||||||
console.log('✅ OpsServer TypedRequest handlers initialized');
|
console.log('✅ OpsServer TypedRequest handlers initialized');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,8 +167,16 @@ export class CertificateHandler {
|
|||||||
issuedAt = new Date(certData.created).toISOString();
|
issuedAt = new Date(certData.created).toISOString();
|
||||||
}
|
}
|
||||||
issuer = 'smartacme-dns-01';
|
issuer = 'smartacme-dns-01';
|
||||||
|
} else if (certData?.publicKey) {
|
||||||
|
// certStore has the cert — parse PEM for expiry
|
||||||
|
try {
|
||||||
|
const x509 = new plugins.crypto.X509Certificate(certData.publicKey);
|
||||||
|
expiryDate = new Date(x509.validTo).toISOString();
|
||||||
|
issuedAt = new Date(x509.validFrom).toISOString();
|
||||||
|
} catch { /* PEM parsing failed */ }
|
||||||
|
status = 'valid';
|
||||||
|
issuer = 'cert-store';
|
||||||
} else if (certData) {
|
} else if (certData) {
|
||||||
// certStore has the cert (no expiry metadata) — it's loaded and serving
|
|
||||||
status = 'valid';
|
status = 'valid';
|
||||||
issuer = 'cert-store';
|
issuer = 'cert-store';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ export * from './stats.handler.js';
|
|||||||
export * from './radius.handler.js';
|
export * from './radius.handler.js';
|
||||||
export * from './email-ops.handler.js';
|
export * from './email-ops.handler.js';
|
||||||
export * from './certificate.handler.js';
|
export * from './certificate.handler.js';
|
||||||
|
export * from './remoteingress.handler.js';
|
||||||
163
ts/opsserver/handlers/remoteingress.handler.ts
Normal file
163
ts/opsserver/handlers/remoteingress.handler.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import * as plugins from '../../plugins.js';
|
||||||
|
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 {
|
||||||
|
// Get all remote ingress edges
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>(
|
||||||
|
'getRemoteIngresses',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
|
||||||
|
if (!manager) {
|
||||||
|
return { edges: [] };
|
||||||
|
}
|
||||||
|
// Return edges without secrets
|
||||||
|
const edges = manager.getAllEdges().map((e) => ({
|
||||||
|
...e,
|
||||||
|
secret: '********', // Never expose secrets via API
|
||||||
|
}));
|
||||||
|
return { edges };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a new remote ingress edge
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>(
|
||||||
|
'createRemoteIngress',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
|
||||||
|
const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
|
||||||
|
|
||||||
|
if (!manager) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
edge: null as any,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const edge = await manager.createEdge(
|
||||||
|
dataArg.name,
|
||||||
|
dataArg.listenPorts,
|
||||||
|
dataArg.tags,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sync allowed edges with the hub
|
||||||
|
if (tunnelManager) {
|
||||||
|
await tunnelManager.syncAllowedEdges();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, edge };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete a remote ingress edge
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>(
|
||||||
|
'deleteRemoteIngress',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
|
||||||
|
const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
|
||||||
|
|
||||||
|
if (!manager) {
|
||||||
|
return { success: false, message: 'RemoteIngress not configured' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleted = await manager.deleteEdge(dataArg.id);
|
||||||
|
if (deleted && tunnelManager) {
|
||||||
|
await tunnelManager.syncAllowedEdges();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: deleted,
|
||||||
|
message: deleted ? undefined : 'Edge not found',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update a remote ingress edge
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>(
|
||||||
|
'updateRemoteIngress',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
|
||||||
|
const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
|
||||||
|
|
||||||
|
if (!manager) {
|
||||||
|
return { success: false, edge: null as any };
|
||||||
|
}
|
||||||
|
|
||||||
|
const edge = await manager.updateEdge(dataArg.id, {
|
||||||
|
name: dataArg.name,
|
||||||
|
listenPorts: dataArg.listenPorts,
|
||||||
|
enabled: dataArg.enabled,
|
||||||
|
tags: dataArg.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!edge) {
|
||||||
|
return { success: false, edge: null as any };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync allowed edges if enabled status changed
|
||||||
|
if (tunnelManager && dataArg.enabled !== undefined) {
|
||||||
|
await tunnelManager.syncAllowedEdges();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, edge: { ...edge, secret: '********' } };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Regenerate secret for an edge
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
|
||||||
|
'regenerateRemoteIngressSecret',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
|
||||||
|
const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
|
||||||
|
|
||||||
|
if (!manager) {
|
||||||
|
return { success: false, secret: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = await manager.regenerateSecret(dataArg.id);
|
||||||
|
if (!secret) {
|
||||||
|
return { success: false, secret: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync allowed edges since secret changed
|
||||||
|
if (tunnelManager) {
|
||||||
|
await tunnelManager.syncAllowedEdges();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, secret };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get runtime status of all edges
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>(
|
||||||
|
'getRemoteIngressStatus',
|
||||||
|
async (dataArg, toolsArg) => {
|
||||||
|
const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
|
||||||
|
if (!tunnelManager) {
|
||||||
|
return { statuses: [] };
|
||||||
|
}
|
||||||
|
return { statuses: tunnelManager.getEdgeStatuses() };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
ts/paths.ts
63
ts/paths.ts
@@ -1,7 +1,6 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
// Base directories
|
// Code/asset paths (not affected by baseDir)
|
||||||
export const baseDir = process.cwd();
|
|
||||||
export const packageDir = plugins.path.join(
|
export const packageDir = plugins.path.join(
|
||||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
'../'
|
'../'
|
||||||
@@ -20,35 +19,37 @@ export const dataDir = process.env.DATA_DIR
|
|||||||
// Default TsmDB path for CacheDb
|
// Default TsmDB path for CacheDb
|
||||||
export const defaultTsmDbPath = plugins.path.join(dcrouterHomeDir, 'tsmdb');
|
export const defaultTsmDbPath = plugins.path.join(dcrouterHomeDir, 'tsmdb');
|
||||||
|
|
||||||
// MTA directories
|
// DNS records directory (only surviving MTA directory reference)
|
||||||
export const keysDir = plugins.path.join(dataDir, 'keys');
|
|
||||||
export const dnsRecordsDir = plugins.path.join(dataDir, 'dns');
|
export const dnsRecordsDir = plugins.path.join(dataDir, 'dns');
|
||||||
export const sentEmailsDir = plugins.path.join(dataDir, 'emails', 'sent');
|
|
||||||
export const receivedEmailsDir = plugins.path.join(dataDir, 'emails', 'received');
|
|
||||||
export const failedEmailsDir = plugins.path.join(dataDir, 'emails', 'failed'); // For failed emails
|
|
||||||
export const logsDir = plugins.path.join(dataDir, 'logs'); // For logs
|
|
||||||
|
|
||||||
// Email template directories
|
/**
|
||||||
export const emailTemplatesDir = plugins.path.join(dataDir, 'templates', 'email');
|
* Resolve all data paths from a given baseDir.
|
||||||
export const MtaAttachmentsDir = plugins.path.join(dataDir, 'attachments'); // For email attachments
|
* When no baseDir is provided, falls back to ~/.serve.zone/dcrouter.
|
||||||
|
* Specific overrides (e.g. DATA_DIR env) take precedence.
|
||||||
// Configuration path
|
*/
|
||||||
export const configPath = process.env.CONFIG_PATH
|
export function resolvePaths(baseDir?: string) {
|
||||||
? process.env.CONFIG_PATH
|
const root = baseDir ?? plugins.path.join(plugins.os.homedir(), '.serve.zone', 'dcrouter');
|
||||||
: plugins.path.join(baseDir, 'config.json');
|
const resolvedDataDir = process.env.DATA_DIR ?? plugins.path.join(root, 'data');
|
||||||
|
return {
|
||||||
// Create directories if they don't exist
|
dcrouterHomeDir: root,
|
||||||
export function ensureDirectories() {
|
dataDir: resolvedDataDir,
|
||||||
// Ensure data directories
|
defaultTsmDbPath: plugins.path.join(root, 'tsmdb'),
|
||||||
plugins.fsUtils.ensureDirSync(dataDir);
|
defaultStoragePath: plugins.path.join(root, 'storage'),
|
||||||
plugins.fsUtils.ensureDirSync(keysDir);
|
dnsRecordsDir: plugins.path.join(resolvedDataDir, 'dns'),
|
||||||
plugins.fsUtils.ensureDirSync(dnsRecordsDir);
|
};
|
||||||
plugins.fsUtils.ensureDirSync(sentEmailsDir);
|
}
|
||||||
plugins.fsUtils.ensureDirSync(receivedEmailsDir);
|
|
||||||
plugins.fsUtils.ensureDirSync(failedEmailsDir);
|
/**
|
||||||
plugins.fsUtils.ensureDirSync(logsDir);
|
* Ensure only the data directories that are actually used exist.
|
||||||
|
*/
|
||||||
// Ensure email template directories
|
export function ensureDataDirectories(resolvedPaths: ReturnType<typeof resolvePaths>) {
|
||||||
plugins.fsUtils.ensureDirSync(emailTemplatesDir);
|
plugins.fsUtils.ensureDirSync(resolvedPaths.dataDir);
|
||||||
plugins.fsUtils.ensureDirSync(MtaAttachmentsDir);
|
plugins.fsUtils.ensureDirSync(resolvedPaths.dnsRecordsDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy wrapper — delegates to ensureDataDirectories with module-level defaults.
|
||||||
|
*/
|
||||||
|
export function ensureDirectories() {
|
||||||
|
ensureDataDirectories(resolvePaths());
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,11 @@ export {
|
|||||||
|
|
||||||
// @serve.zone scope
|
// @serve.zone scope
|
||||||
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||||
|
import * as remoteingress from '@serve.zone/remoteingress';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
servezoneInterfaces
|
servezoneInterfaces,
|
||||||
|
remoteingress,
|
||||||
}
|
}
|
||||||
|
|
||||||
// @api.global scope
|
// @api.global scope
|
||||||
|
|||||||
160
ts/remoteingress/classes.remoteingress-manager.ts
Normal file
160
ts/remoteingress/classes.remoteingress-manager.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
||||||
|
import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
|
||||||
|
|
||||||
|
const STORAGE_PREFIX = '/remote-ingress/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages CRUD for remote ingress edge registrations.
|
||||||
|
* Persists edge configs via StorageManager and provides
|
||||||
|
* the allowed edges list for the Rust hub.
|
||||||
|
*/
|
||||||
|
export class RemoteIngressManager {
|
||||||
|
private storageManager: StorageManager;
|
||||||
|
private edges: Map<string, IRemoteIngress> = new Map();
|
||||||
|
|
||||||
|
constructor(storageManager: StorageManager) {
|
||||||
|
this.storageManager = storageManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all edge registrations from storage into memory.
|
||||||
|
*/
|
||||||
|
public async initialize(): Promise<void> {
|
||||||
|
const keys = await this.storageManager.list(STORAGE_PREFIX);
|
||||||
|
for (const key of keys) {
|
||||||
|
const edge = await this.storageManager.getJSON<IRemoteIngress>(key);
|
||||||
|
if (edge) {
|
||||||
|
this.edges.set(edge.id, edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new edge registration.
|
||||||
|
*/
|
||||||
|
public async createEdge(
|
||||||
|
name: string,
|
||||||
|
listenPorts: number[],
|
||||||
|
tags?: string[],
|
||||||
|
): Promise<IRemoteIngress> {
|
||||||
|
const id = plugins.uuid.v4();
|
||||||
|
const secret = plugins.crypto.randomBytes(32).toString('hex');
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
const edge: IRemoteIngress = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
secret,
|
||||||
|
listenPorts,
|
||||||
|
enabled: true,
|
||||||
|
tags: tags || [],
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||||||
|
this.edges.set(id, edge);
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an edge by ID.
|
||||||
|
*/
|
||||||
|
public getEdge(id: string): IRemoteIngress | undefined {
|
||||||
|
return this.edges.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all edge registrations.
|
||||||
|
*/
|
||||||
|
public getAllEdges(): IRemoteIngress[] {
|
||||||
|
return Array.from(this.edges.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an edge registration.
|
||||||
|
*/
|
||||||
|
public async updateEdge(
|
||||||
|
id: string,
|
||||||
|
updates: {
|
||||||
|
name?: string;
|
||||||
|
listenPorts?: number[];
|
||||||
|
enabled?: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
},
|
||||||
|
): Promise<IRemoteIngress | null> {
|
||||||
|
const edge = this.edges.get(id);
|
||||||
|
if (!edge) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.name !== undefined) edge.name = updates.name;
|
||||||
|
if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
|
||||||
|
if (updates.enabled !== undefined) edge.enabled = updates.enabled;
|
||||||
|
if (updates.tags !== undefined) edge.tags = updates.tags;
|
||||||
|
edge.updatedAt = Date.now();
|
||||||
|
|
||||||
|
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||||||
|
this.edges.set(id, edge);
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an edge registration.
|
||||||
|
*/
|
||||||
|
public async deleteEdge(id: string): Promise<boolean> {
|
||||||
|
if (!this.edges.has(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await this.storageManager.delete(`${STORAGE_PREFIX}${id}`);
|
||||||
|
this.edges.delete(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerate the secret for an edge.
|
||||||
|
*/
|
||||||
|
public async regenerateSecret(id: string): Promise<string | null> {
|
||||||
|
const edge = this.edges.get(id);
|
||||||
|
if (!edge) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
edge.secret = plugins.crypto.randomBytes(32).toString('hex');
|
||||||
|
edge.updatedAt = Date.now();
|
||||||
|
|
||||||
|
await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
|
||||||
|
this.edges.set(id, edge);
|
||||||
|
return edge.secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify an edge's secret using constant-time comparison.
|
||||||
|
*/
|
||||||
|
public verifySecret(id: string, secret: string): boolean {
|
||||||
|
const edge = this.edges.get(id);
|
||||||
|
if (!edge) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const expected = Buffer.from(edge.secret);
|
||||||
|
const provided = Buffer.from(secret);
|
||||||
|
if (expected.length !== provided.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return plugins.crypto.timingSafeEqual(expected, provided);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of allowed edges (enabled only) for the Rust hub.
|
||||||
|
*/
|
||||||
|
public getAllowedEdges(): Array<{ id: string; secret: string }> {
|
||||||
|
const result: Array<{ id: string; secret: string }> = [];
|
||||||
|
for (const edge of this.edges.values()) {
|
||||||
|
if (edge.enabled) {
|
||||||
|
result.push({ id: edge.id, secret: edge.secret });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
126
ts/remoteingress/classes.tunnel-manager.ts
Normal file
126
ts/remoteingress/classes.tunnel-manager.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import type { IRemoteIngressStatus } from '../../ts_interfaces/data/remoteingress.js';
|
||||||
|
import type { RemoteIngressManager } from './classes.remoteingress-manager.js';
|
||||||
|
|
||||||
|
export interface ITunnelManagerConfig {
|
||||||
|
tunnelPort?: number;
|
||||||
|
targetHost?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the RemoteIngressHub instance and tracks connected edge statuses.
|
||||||
|
*/
|
||||||
|
export class TunnelManager {
|
||||||
|
private hub: InstanceType<typeof plugins.remoteingress.RemoteIngressHub>;
|
||||||
|
private manager: RemoteIngressManager;
|
||||||
|
private config: ITunnelManagerConfig;
|
||||||
|
private edgeStatuses: Map<string, IRemoteIngressStatus> = new Map();
|
||||||
|
|
||||||
|
constructor(manager: RemoteIngressManager, config: ITunnelManagerConfig = {}) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.config = config;
|
||||||
|
this.hub = new plugins.remoteingress.RemoteIngressHub();
|
||||||
|
|
||||||
|
// Listen for edge connect/disconnect events
|
||||||
|
this.hub.on('edgeConnected', (data: { edgeId: string }) => {
|
||||||
|
const existing = this.edgeStatuses.get(data.edgeId);
|
||||||
|
this.edgeStatuses.set(data.edgeId, {
|
||||||
|
edgeId: data.edgeId,
|
||||||
|
connected: true,
|
||||||
|
publicIp: existing?.publicIp ?? null,
|
||||||
|
activeTunnels: 0,
|
||||||
|
lastHeartbeat: Date.now(),
|
||||||
|
connectedAt: Date.now(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hub.on('edgeDisconnected', (data: { edgeId: string }) => {
|
||||||
|
const existing = this.edgeStatuses.get(data.edgeId);
|
||||||
|
if (existing) {
|
||||||
|
existing.connected = false;
|
||||||
|
existing.activeTunnels = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hub.on('streamOpened', (data: { edgeId: string; streamId: number }) => {
|
||||||
|
const existing = this.edgeStatuses.get(data.edgeId);
|
||||||
|
if (existing) {
|
||||||
|
existing.activeTunnels++;
|
||||||
|
existing.lastHeartbeat = Date.now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hub.on('streamClosed', (data: { edgeId: string; streamId: number }) => {
|
||||||
|
const existing = this.edgeStatuses.get(data.edgeId);
|
||||||
|
if (existing && existing.activeTunnels > 0) {
|
||||||
|
existing.activeTunnels--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the tunnel hub and load allowed edges.
|
||||||
|
*/
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
await this.hub.start({
|
||||||
|
tunnelPort: this.config.tunnelPort ?? 8443,
|
||||||
|
targetHost: this.config.targetHost ?? '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send allowed edges to the hub
|
||||||
|
await this.syncAllowedEdges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the tunnel hub.
|
||||||
|
*/
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
await this.hub.stop();
|
||||||
|
this.edgeStatuses.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync allowed edges from the manager to the hub.
|
||||||
|
* Call this after creating/deleting/updating edges.
|
||||||
|
*/
|
||||||
|
public async syncAllowedEdges(): Promise<void> {
|
||||||
|
const edges = this.manager.getAllowedEdges();
|
||||||
|
await this.hub.updateAllowedEdges(edges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get runtime statuses for all known edges.
|
||||||
|
*/
|
||||||
|
public getEdgeStatuses(): IRemoteIngressStatus[] {
|
||||||
|
return Array.from(this.edgeStatuses.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get status for a specific edge.
|
||||||
|
*/
|
||||||
|
public getEdgeStatus(edgeId: string): IRemoteIngressStatus | undefined {
|
||||||
|
return this.edgeStatuses.get(edgeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the count of connected edges.
|
||||||
|
*/
|
||||||
|
public getConnectedCount(): number {
|
||||||
|
let count = 0;
|
||||||
|
for (const status of this.edgeStatuses.values()) {
|
||||||
|
if (status.connected) count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total number of active tunnels across all edges.
|
||||||
|
*/
|
||||||
|
public getTotalActiveTunnels(): number {
|
||||||
|
let total = 0;
|
||||||
|
for (const status of this.edgeStatuses.values()) {
|
||||||
|
total += status.activeTunnels;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
ts/remoteingress/index.ts
Normal file
2
ts/remoteingress/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './classes.remoteingress-manager.js';
|
||||||
|
export * from './classes.tunnel-manager.js';
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from '../paths.js';
|
|
||||||
import { logger } from '../logger.js';
|
import { logger } from '../logger.js';
|
||||||
import { Email, type Core } from '@push.rocks/smartmta';
|
import { Email, type Core } from '@push.rocks/smartmta';
|
||||||
type IAttachment = Core.IAttachment;
|
type IAttachment = Core.IAttachment;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './auth.js';
|
export * from './auth.js';
|
||||||
export * from './stats.js';
|
export * from './stats.js';
|
||||||
|
export * from './remoteingress.js';
|
||||||
25
ts_interfaces/data/remoteingress.ts
Normal file
25
ts_interfaces/data/remoteingress.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* A stored remote ingress edge registration.
|
||||||
|
*/
|
||||||
|
export interface IRemoteIngress {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
secret: string;
|
||||||
|
listenPorts: number[];
|
||||||
|
enabled: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
createdAt: number;
|
||||||
|
updatedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime status of a remote ingress edge.
|
||||||
|
*/
|
||||||
|
export interface IRemoteIngressStatus {
|
||||||
|
edgeId: string;
|
||||||
|
connected: boolean;
|
||||||
|
publicIp: string | null;
|
||||||
|
activeTunnels: number;
|
||||||
|
lastHeartbeat: number | null;
|
||||||
|
connectedAt: number | null;
|
||||||
|
}
|
||||||
@@ -6,3 +6,4 @@ export * from './combined.stats.js';
|
|||||||
export * from './radius.js';
|
export * from './radius.js';
|
||||||
export * from './email-ops.js';
|
export * from './email-ops.js';
|
||||||
export * from './certificate.js';
|
export * from './certificate.js';
|
||||||
|
export * from './remoteingress.js';
|
||||||
117
ts_interfaces/requests/remoteingress.ts
Normal file
117
ts_interfaces/requests/remoteingress.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import * as authInterfaces from '../data/auth.js';
|
||||||
|
import type { IRemoteIngress, IRemoteIngressStatus } from '../data/remoteingress.js';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Remote Ingress Edge Management
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new remote ingress edge registration.
|
||||||
|
*/
|
||||||
|
export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_CreateRemoteIngress
|
||||||
|
> {
|
||||||
|
method: 'createRemoteIngress';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
name: string;
|
||||||
|
listenPorts: number[];
|
||||||
|
tags?: string[];
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
edge: IRemoteIngress;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a remote ingress edge registration.
|
||||||
|
*/
|
||||||
|
export interface IReq_DeleteRemoteIngress extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_DeleteRemoteIngress
|
||||||
|
> {
|
||||||
|
method: 'deleteRemoteIngress';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a remote ingress edge registration.
|
||||||
|
*/
|
||||||
|
export interface IReq_UpdateRemoteIngress extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_UpdateRemoteIngress
|
||||||
|
> {
|
||||||
|
method: 'updateRemoteIngress';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
listenPorts?: number[];
|
||||||
|
enabled?: boolean;
|
||||||
|
tags?: string[];
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
edge: IRemoteIngress;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerate the secret for a remote ingress edge.
|
||||||
|
*/
|
||||||
|
export interface IReq_RegenerateRemoteIngressSecret extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_RegenerateRemoteIngressSecret
|
||||||
|
> {
|
||||||
|
method: 'regenerateRemoteIngressSecret';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
secret: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all remote ingress edge registrations.
|
||||||
|
*/
|
||||||
|
export interface IReq_GetRemoteIngresses extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetRemoteIngresses
|
||||||
|
> {
|
||||||
|
method: 'getRemoteIngresses';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
edges: IRemoteIngress[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get runtime status of all remote ingress edges.
|
||||||
|
*/
|
||||||
|
export interface IReq_GetRemoteIngressStatus extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetRemoteIngressStatus
|
||||||
|
> {
|
||||||
|
method: 'getRemoteIngressStatus';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
statuses: IRemoteIngressStatus[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '6.2.2',
|
version: '6.4.5',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
|
|||||||
// Determine initial view from URL path
|
// Determine initial view from URL path
|
||||||
const getInitialView = (): string => {
|
const getInitialView = (): string => {
|
||||||
const path = typeof window !== 'undefined' ? window.location.pathname : '/';
|
const path = typeof window !== 'undefined' ? window.location.pathname : '/';
|
||||||
const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates'];
|
const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates', 'remoteingress'];
|
||||||
const segments = path.split('/').filter(Boolean);
|
const segments = path.split('/').filter(Boolean);
|
||||||
const view = segments[0];
|
const view = segments[0];
|
||||||
return validViews.includes(view) ? view : 'overview';
|
return validViews.includes(view) ? view : 'overview';
|
||||||
@@ -192,6 +192,34 @@ export const certificateStatePart = await appState.getStatePart<ICertificateStat
|
|||||||
'soft'
|
'soft'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Remote Ingress State
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface IRemoteIngressState {
|
||||||
|
edges: interfaces.data.IRemoteIngress[];
|
||||||
|
statuses: interfaces.data.IRemoteIngressStatus[];
|
||||||
|
selectedEdgeId: string | null;
|
||||||
|
newEdgeSecret: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
lastUpdated: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const remoteIngressStatePart = await appState.getStatePart<IRemoteIngressState>(
|
||||||
|
'remoteIngress',
|
||||||
|
{
|
||||||
|
edges: [],
|
||||||
|
statuses: [],
|
||||||
|
selectedEdgeId: null,
|
||||||
|
newEdgeSecret: null,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
lastUpdated: 0,
|
||||||
|
},
|
||||||
|
'soft'
|
||||||
|
);
|
||||||
|
|
||||||
// Actions for state management
|
// Actions for state management
|
||||||
interface IActionContext {
|
interface IActionContext {
|
||||||
identity: interfaces.data.IIdentity | null;
|
identity: interfaces.data.IIdentity | null;
|
||||||
@@ -378,6 +406,13 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If switching to remoteingress view, ensure we fetch edge data
|
||||||
|
if (viewName === 'remoteingress' && currentState.activeView !== 'remoteingress') {
|
||||||
|
setTimeout(() => {
|
||||||
|
remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...currentState,
|
...currentState,
|
||||||
activeView: viewName,
|
activeView: viewName,
|
||||||
@@ -745,6 +780,150 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Remote Ingress Actions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
|
||||||
|
const context = getActionContext();
|
||||||
|
const currentState = statePartArg.getState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_GetRemoteIngresses
|
||||||
|
>('/typedrequest', 'getRemoteIngresses');
|
||||||
|
|
||||||
|
const statusRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_GetRemoteIngressStatus
|
||||||
|
>('/typedrequest', 'getRemoteIngressStatus');
|
||||||
|
|
||||||
|
const [edgesResponse, statusResponse] = await Promise.all([
|
||||||
|
edgesRequest.fire({ identity: context.identity }),
|
||||||
|
statusRequest.fire({ identity: context.identity }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
edges: edgesResponse.edges,
|
||||||
|
statuses: statusResponse.statuses,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
lastUpdated: Date.now(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
isLoading: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to fetch remote ingress data',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||||
|
name: string;
|
||||||
|
listenPorts: number[];
|
||||||
|
tags?: string[];
|
||||||
|
}>(async (statePartArg, dataArg) => {
|
||||||
|
const context = getActionContext();
|
||||||
|
const currentState = statePartArg.getState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_CreateRemoteIngress
|
||||||
|
>('/typedrequest', 'createRemoteIngress');
|
||||||
|
|
||||||
|
const response = await request.fire({
|
||||||
|
identity: context.identity,
|
||||||
|
name: dataArg.name,
|
||||||
|
listenPorts: dataArg.listenPorts,
|
||||||
|
tags: dataArg.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// Refresh the list and store the new secret for display
|
||||||
|
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||||
|
return {
|
||||||
|
...statePartArg.getState(),
|
||||||
|
newEdgeSecret: response.edge.secret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to create edge',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>(
|
||||||
|
async (statePartArg, edgeId) => {
|
||||||
|
const context = getActionContext();
|
||||||
|
const currentState = statePartArg.getState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_DeleteRemoteIngress
|
||||||
|
>('/typedrequest', 'deleteRemoteIngress');
|
||||||
|
|
||||||
|
await request.fire({
|
||||||
|
identity: context.identity,
|
||||||
|
id: edgeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||||
|
return statePartArg.getState();
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to delete edge',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.createAction<string>(
|
||||||
|
async (statePartArg, edgeId) => {
|
||||||
|
const context = getActionContext();
|
||||||
|
const currentState = statePartArg.getState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_RegenerateRemoteIngressSecret
|
||||||
|
>('/typedrequest', 'regenerateRemoteIngressSecret');
|
||||||
|
|
||||||
|
const response = await request.fire({
|
||||||
|
identity: context.identity,
|
||||||
|
id: edgeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
newEdgeSecret: response.secret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentState;
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...currentState,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to regenerate secret',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const clearNewEdgeSecretAction = remoteIngressStatePart.createAction(
|
||||||
|
async (statePartArg) => {
|
||||||
|
return {
|
||||||
|
...statePartArg.getState(),
|
||||||
|
newEdgeSecret: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Combined refresh action for efficient polling
|
// Combined refresh action for efficient polling
|
||||||
async function dispatchCombinedRefreshAction() {
|
async function dispatchCombinedRefreshAction() {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ export * from './ops-view-logs.js';
|
|||||||
export * from './ops-view-config.js';
|
export * from './ops-view-config.js';
|
||||||
export * from './ops-view-security.js';
|
export * from './ops-view-security.js';
|
||||||
export * from './ops-view-certificates.js';
|
export * from './ops-view-certificates.js';
|
||||||
|
export * from './ops-view-remoteingress.js';
|
||||||
export * from './shared/index.js';
|
export * from './shared/index.js';
|
||||||
@@ -20,6 +20,7 @@ import { OpsViewLogs } from './ops-view-logs.js';
|
|||||||
import { OpsViewConfig } from './ops-view-config.js';
|
import { OpsViewConfig } from './ops-view-config.js';
|
||||||
import { OpsViewSecurity } from './ops-view-security.js';
|
import { OpsViewSecurity } from './ops-view-security.js';
|
||||||
import { OpsViewCertificates } from './ops-view-certificates.js';
|
import { OpsViewCertificates } from './ops-view-certificates.js';
|
||||||
|
import { OpsViewRemoteIngress } from './ops-view-remoteingress.js';
|
||||||
|
|
||||||
@customElement('ops-dashboard')
|
@customElement('ops-dashboard')
|
||||||
export class OpsDashboard extends DeesElement {
|
export class OpsDashboard extends DeesElement {
|
||||||
@@ -66,6 +67,10 @@ export class OpsDashboard extends DeesElement {
|
|||||||
name: 'Certificates',
|
name: 'Certificates',
|
||||||
element: OpsViewCertificates,
|
element: OpsViewCertificates,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'RemoteIngress',
|
||||||
|
element: OpsViewRemoteIngress,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
292
ts_web/elements/ops-view-remoteingress.ts
Normal file
292
ts_web/elements/ops-view-remoteingress.ts
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
html,
|
||||||
|
customElement,
|
||||||
|
type TemplateResult,
|
||||||
|
css,
|
||||||
|
state,
|
||||||
|
cssManager,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
import * as appstate from '../appstate.js';
|
||||||
|
import * as interfaces from '../../dist_ts_interfaces/index.js';
|
||||||
|
import { viewHostCss } from './shared/css.js';
|
||||||
|
import { type IStatsTile } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'ops-view-remoteingress': OpsViewRemoteIngress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('ops-view-remoteingress')
|
||||||
|
export class OpsViewRemoteIngress extends DeesElement {
|
||||||
|
@state()
|
||||||
|
accessor riState: appstate.IRemoteIngressState = appstate.remoteIngressStatePart.getState();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const sub = appstate.remoteIngressStatePart.state.subscribe((newState) => {
|
||||||
|
this.riState = newState;
|
||||||
|
});
|
||||||
|
this.rxSubscriptions.push(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectedCallback() {
|
||||||
|
await super.connectedCallback();
|
||||||
|
await appstate.remoteIngressStatePart.dispatchAction(appstate.fetchRemoteIngressAction, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
viewHostCss,
|
||||||
|
css`
|
||||||
|
.remoteIngressContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusBadge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusBadge.connected {
|
||||||
|
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
||||||
|
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusBadge.disconnected {
|
||||||
|
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
|
||||||
|
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusBadge.disabled {
|
||||||
|
background: ${cssManager.bdTheme('#f3f4f6', '#374151')};
|
||||||
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.secretDialog {
|
||||||
|
padding: 16px;
|
||||||
|
background: ${cssManager.bdTheme('#fffbeb', '#1c1917')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#fbbf24', '#92400e')};
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secretDialog code {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: ${cssManager.bdTheme('#1f2937', '#111827')};
|
||||||
|
color: #10b981;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
word-break: break-all;
|
||||||
|
margin: 8px 0;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secretDialog .warning {
|
||||||
|
font-size: 12px;
|
||||||
|
color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portsDisplay {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portBadge {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: ${cssManager.bdTheme('#eff6ff', '#172554')};
|
||||||
|
color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
const totalEdges = this.riState.edges.length;
|
||||||
|
const connectedEdges = this.riState.statuses.filter(s => s.connected).length;
|
||||||
|
const disconnectedEdges = totalEdges - connectedEdges;
|
||||||
|
const activeTunnels = this.riState.statuses.reduce((sum, s) => sum + s.activeTunnels, 0);
|
||||||
|
|
||||||
|
const statsTiles: IStatsTile[] = [
|
||||||
|
{
|
||||||
|
id: 'totalEdges',
|
||||||
|
title: 'Total Edges',
|
||||||
|
type: 'number',
|
||||||
|
value: totalEdges,
|
||||||
|
icon: 'lucide:server',
|
||||||
|
description: 'Registered edge nodes',
|
||||||
|
color: '#3b82f6',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'connectedEdges',
|
||||||
|
title: 'Connected',
|
||||||
|
type: 'number',
|
||||||
|
value: connectedEdges,
|
||||||
|
icon: 'lucide:link',
|
||||||
|
description: 'Currently connected edges',
|
||||||
|
color: '#10b981',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'disconnectedEdges',
|
||||||
|
title: 'Disconnected',
|
||||||
|
type: 'number',
|
||||||
|
value: disconnectedEdges,
|
||||||
|
icon: 'lucide:unlink',
|
||||||
|
description: 'Offline edge nodes',
|
||||||
|
color: disconnectedEdges > 0 ? '#ef4444' : '#6b7280',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'activeTunnels',
|
||||||
|
title: 'Active Tunnels',
|
||||||
|
type: 'number',
|
||||||
|
value: activeTunnels,
|
||||||
|
icon: 'lucide:cable',
|
||||||
|
description: 'Active client connections',
|
||||||
|
color: '#8b5cf6',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ops-sectionheading>Remote Ingress</ops-sectionheading>
|
||||||
|
|
||||||
|
${this.riState.newEdgeSecret ? html`
|
||||||
|
<div class="secretDialog">
|
||||||
|
<strong>Edge Secret (copy now - shown only once):</strong>
|
||||||
|
<code>${this.riState.newEdgeSecret}</code>
|
||||||
|
<div class="warning">This secret will not be shown again. Save it securely.</div>
|
||||||
|
<dees-button
|
||||||
|
@click=${() => appstate.remoteIngressStatePart.dispatchAction(appstate.clearNewEdgeSecretAction, null)}
|
||||||
|
>Dismiss</dees-button>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="remoteIngressContainer">
|
||||||
|
<dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
|
||||||
|
|
||||||
|
<dees-table
|
||||||
|
.heading1=${'Edge Nodes'}
|
||||||
|
.heading2=${'Manage remote ingress edge registrations'}
|
||||||
|
.data=${this.riState.edges}
|
||||||
|
.displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
|
||||||
|
name: edge.name,
|
||||||
|
status: this.getEdgeStatusHtml(edge),
|
||||||
|
publicIp: this.getEdgePublicIp(edge.id),
|
||||||
|
ports: this.getPortsHtml(edge.listenPorts),
|
||||||
|
tunnels: this.getEdgeTunnelCount(edge.id),
|
||||||
|
lastHeartbeat: this.getLastHeartbeat(edge.id),
|
||||||
|
})}
|
||||||
|
.dataActions=${[
|
||||||
|
{
|
||||||
|
name: 'Regenerate Secret',
|
||||||
|
iconName: 'lucide:key',
|
||||||
|
type: ['row'],
|
||||||
|
action: async (edge: interfaces.data.IRemoteIngress) => {
|
||||||
|
await appstate.remoteIngressStatePart.dispatchAction(
|
||||||
|
appstate.regenerateRemoteIngressSecretAction,
|
||||||
|
edge.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
iconName: 'lucide:trash2',
|
||||||
|
type: ['row'],
|
||||||
|
action: async (edge: interfaces.data.IRemoteIngress) => {
|
||||||
|
await appstate.remoteIngressStatePart.dispatchAction(
|
||||||
|
appstate.deleteRemoteIngressAction,
|
||||||
|
edge.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
.createNewAction=${async () => {
|
||||||
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||||
|
const result = await DeesModal.createAndShow({
|
||||||
|
heading: 'Create Edge Node',
|
||||||
|
content: html`
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
||||||
|
<dees-input-text .key=${'listenPorts'} .label=${'Listen Ports (comma-separated)'} .required=${true} .value=${'443,25'}></dees-input-text>
|
||||||
|
<dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
|
||||||
|
</dees-form>
|
||||||
|
`,
|
||||||
|
menuOptions: [],
|
||||||
|
});
|
||||||
|
if (result) {
|
||||||
|
const formData = result as any;
|
||||||
|
const ports = (formData.name ? formData.listenPorts : '443')
|
||||||
|
.split(',')
|
||||||
|
.map((p: string) => parseInt(p.trim(), 10))
|
||||||
|
.filter((p: number) => !isNaN(p));
|
||||||
|
const tags = formData.tags
|
||||||
|
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
||||||
|
: undefined;
|
||||||
|
await appstate.remoteIngressStatePart.dispatchAction(
|
||||||
|
appstate.createRemoteIngressAction,
|
||||||
|
{
|
||||||
|
name: formData.name,
|
||||||
|
listenPorts: ports,
|
||||||
|
tags,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEdgeStatus(edgeId: string): interfaces.data.IRemoteIngressStatus | undefined {
|
||||||
|
return this.riState.statuses.find(s => s.edgeId === edgeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEdgeStatusHtml(edge: interfaces.data.IRemoteIngress): TemplateResult {
|
||||||
|
if (!edge.enabled) {
|
||||||
|
return html`<span class="statusBadge disabled">Disabled</span>`;
|
||||||
|
}
|
||||||
|
const status = this.getEdgeStatus(edge.id);
|
||||||
|
if (status?.connected) {
|
||||||
|
return html`<span class="statusBadge connected">Connected</span>`;
|
||||||
|
}
|
||||||
|
return html`<span class="statusBadge disconnected">Disconnected</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEdgePublicIp(edgeId: string): string {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
return status?.publicIp || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPortsHtml(ports: number[]): TemplateResult {
|
||||||
|
return html`<div class="portsDisplay">${ports.map(p => html`<span class="portBadge">${p}</span>`)}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEdgeTunnelCount(edgeId: string): number {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
return status?.activeTunnels || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLastHeartbeat(edgeId: string): string {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
if (!status?.lastHeartbeat) return '-';
|
||||||
|
const ago = Date.now() - status.lastHeartbeat;
|
||||||
|
if (ago < 60000) return `${Math.floor(ago / 1000)}s ago`;
|
||||||
|
if (ago < 3600000) return `${Math.floor(ago / 60000)}m ago`;
|
||||||
|
return `${Math.floor(ago / 3600000)}h ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import * as appstate from './appstate.js';
|
|||||||
|
|
||||||
const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
|
const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
|
||||||
|
|
||||||
export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates'] as const;
|
export const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates', 'remoteingress'] as const;
|
||||||
export const validEmailFolders = ['queued', 'sent', 'failed', 'security'] as const;
|
export const validEmailFolders = ['queued', 'sent', 'failed', 'security'] as const;
|
||||||
|
|
||||||
export type TValidView = typeof validViews[number];
|
export type TValidView = typeof validViews[number];
|
||||||
|
|||||||
Reference in New Issue
Block a user