feat(remote-ingress): add Remote Ingress hub integration, OpsServer UI, APIs, and docs

This commit is contained in:
2026-02-18 18:47:18 +00:00
parent 86e6c4f600
commit dce1de8c4b
13 changed files with 456 additions and 43 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## 2026-02-18 - 6.12.0 - feat(remote-ingress)
add Remote Ingress hub integration, OpsServer UI, APIs, and docs
- Integrates RemoteIngress (hub/tunnel) into DcRouter: runtime manager, tunnel manager and Rust data plane references added
- Bumps dependency @serve.zone/remoteingress to ^3.3.0
- Adds configuration defaults and IDcRouterOptions.remoteIngressConfig with tunnelPort/hubDomain/tls fields
- Introduces OpsServer API endpoints and TypedRequest methods for remote ingress: getRemoteIngresses, createRemoteIngress, updateRemoteIngress, deleteRemoteIngress, regenerateRemoteIngressSecret, getRemoteIngressStatus, getRemoteIngressConnectionToken
- UI updates: new Remote Ingress dashboard view, connection token generation & copy (clipboard API + fallback), auto-derived ports display, and toast notifications
- State/API rename: newEdgeSecret -> newEdgeId and clearNewEdgeIdAction; appstate fetchConnectionToken usage
- Documentation: README, ts/ and ts_web readmes, and ts_interfaces updated with interfaces and examples for Remote Ingress
- Minor UI icon updates (search -> fa:magnifyingGlass, clipboard icon casing) and other doc/README improvements
## 2026-02-18 - 6.11.0 - feat(remoteingress) ## 2026-02-18 - 6.11.0 - feat(remoteingress)
add ability to generate remote ingress connection tokens and UI copy action; add hubDomain config option; update remoteingress dependency to ^3.1.1 add ability to generate remote ingress connection tokens and UI copy action; add hubDomain config option; update remoteingress dependency to ^3.1.1

View File

@@ -56,7 +56,7 @@
"@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.1.1", "@serve.zone/remoteingress": "^3.3.0",
"@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"

10
pnpm-lock.yaml generated
View File

@@ -96,8 +96,8 @@ importers:
specifier: ^5.3.0 specifier: ^5.3.0
version: 5.3.0 version: 5.3.0
'@serve.zone/remoteingress': '@serve.zone/remoteingress':
specifier: ^3.1.1 specifier: ^3.3.0
version: 3.1.1 version: 3.3.0
'@tsclass/tsclass': '@tsclass/tsclass':
specifier: ^9.3.0 specifier: ^9.3.0
version: 9.3.0 version: 9.3.0
@@ -1340,8 +1340,8 @@ 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.1.1': '@serve.zone/remoteingress@3.3.0':
resolution: {integrity: sha512-tTN3hkLmfL8KeIEu7a685xNlESEZ548aFzKn+44yeUwABuQV5w3P39Pk2U3KAGB0K2prnpuzBGuoMaEIicE9Ew==} resolution: {integrity: sha512-nmw0F+Otrg78Xai9G3qLcP3NP4VkGPGm/6IGJmrXEgx3Z+ewh5Rhs1/rtN0mJFNXP77LZz1HuEBgR8aWbSHFQw==}
'@sindresorhus/is@5.6.0': '@sindresorhus/is@5.6.0':
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
@@ -6830,7 +6830,7 @@ 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.1.1': '@serve.zone/remoteingress@3.3.0':
dependencies: dependencies:
'@push.rocks/qenv': 6.1.3 '@push.rocks/qenv': 6.1.3
'@push.rocks/smartrust': 1.2.1 '@push.rocks/smartrust': 1.2.1

173
readme.md
View File

@@ -4,7 +4,7 @@
**dcrouter: The all-in-one gateway for your datacenter.** 🚀 **dcrouter: The all-in-one gateway for your datacenter.** 🚀
A comprehensive traffic routing solution that provides unified gateway capabilities for HTTP/HTTPS, TCP/SNI, email (SMTP), DNS, and RADIUS protocols. Designed for enterprises requiring robust traffic management, automatic TLS certificate provisioning, and enterprise-grade email infrastructure — all from a single process. A comprehensive traffic routing solution that provides unified gateway capabilities for HTTP/HTTPS, TCP/SNI, email (SMTP), DNS, RADIUS, and remote edge ingress — all from a single process. Designed for enterprises requiring robust traffic management, automatic TLS certificate provisioning, distributed edge networking, and enterprise-grade email infrastructure.
## Issue Reporting and Security ## Issue Reporting and Security
@@ -21,6 +21,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- [Email System](#email-system) - [Email System](#email-system)
- [DNS Server](#dns-server) - [DNS Server](#dns-server)
- [RADIUS Server](#radius-server) - [RADIUS Server](#radius-server)
- [Remote Ingress](#remote-ingress)
- [Certificate Management](#certificate-management) - [Certificate Management](#certificate-management)
- [Storage & Caching](#storage--caching) - [Storage & Caching](#storage--caching)
- [Security Features](#security-features) - [Security Features](#security-features)
@@ -60,6 +61,14 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- **RADIUS accounting** for session tracking, traffic metering, and billing - **RADIUS accounting** for session tracking, traffic metering, and billing
- **Real-time management** via OpsServer API - **Real-time management** via OpsServer API
### 🌍 Remote Ingress (powered by [remoteingress](https://code.foss.global/serve.zone/remoteingress))
- **Distributed edge networking** — accept traffic at remote edge nodes and tunnel it to the hub
- **Edge registration CRUD** with secret-based authentication
- **Auto-derived ports** — edges automatically pick up ports from routes tagged with `remoteIngress.enabled`
- **Connection tokens** — generate a single opaque base64url token containing hubHost, hubPort, edgeId, and secret for easy edge provisioning
- **Real-time status monitoring** — connected/disconnected state, public IP, active tunnels, heartbeat tracking
- **OpsServer dashboard** with enable/disable, edit, secret regeneration, token copy, and delete actions
### ⚡ High Performance ### ⚡ High Performance
- **Rust-powered proxy engine** via SmartProxy for maximum throughput - **Rust-powered proxy engine** via SmartProxy for maximum throughput
- **Rust-powered MTA engine** via smartmta (TypeScript + Rust hybrid) for reliable email delivery - **Rust-powered MTA engine** via smartmta (TypeScript + Rust hybrid) for reliable email delivery
@@ -76,8 +85,9 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
### 🖥️ OpsServer Dashboard ### 🖥️ OpsServer Dashboard
- **Web-based management interface** with real-time monitoring - **Web-based management interface** with real-time monitoring
- **JWT authentication** with session persistence - **JWT authentication** with session persistence
- **Live views** for connections, email queues, DNS queries, RADIUS sessions, certificates, and security events - **Live views** for connections, email queues, DNS queries, RADIUS sessions, certificates, remote ingress edges, and security events
- **Domain-centric certificate overview** with backoff status and one-click reprovisioning - **Domain-centric certificate overview** with backoff status and one-click reprovisioning
- **Remote ingress management** with connection token generation and one-click copy
- **Read-only configuration display** — DcRouter is configured through code - **Read-only configuration display** — DcRouter is configured through code
## Installation ## Installation
@@ -219,6 +229,13 @@ const router = new DcRouter({
accounting: { enabled: true, retentionDays: 30 } accounting: { enabled: true, retentionDays: 30 }
}, },
// Remote Ingress — edge nodes tunnel traffic to this hub
remoteIngressConfig: {
enabled: true,
tunnelPort: 8443,
hubDomain: 'hub.example.com',
},
// Persistent storage // Persistent storage
storage: { fsPath: '/var/lib/dcrouter/data' }, storage: { fsPath: '/var/lib/dcrouter/data' },
@@ -246,6 +263,7 @@ graph TB
TCP[TCP Clients] TCP[TCP Clients]
DNS[DNS Queries] DNS[DNS Queries]
RAD[RADIUS Clients] RAD[RADIUS Clients]
EDGE[Edge Nodes]
end end
subgraph "DcRouter Core" subgraph "DcRouter Core"
@@ -254,6 +272,7 @@ graph TB
ES[smartmta Email Server<br/><i>TypeScript + Rust</i>] ES[smartmta Email Server<br/><i>TypeScript + Rust</i>]
DS[SmartDNS Server<br/><i>Rust-powered</i>] DS[SmartDNS Server<br/><i>Rust-powered</i>]
RS[SmartRadius Server] RS[SmartRadius Server]
RI[RemoteIngress Hub<br/><i>Rust data plane</i>]
CM[Certificate Manager<br/><i>smartacme v9</i>] CM[Certificate Manager<br/><i>smartacme v9</i>]
OS[OpsServer Dashboard] OS[OpsServer Dashboard]
MM[Metrics Manager] MM[Metrics Manager]
@@ -273,11 +292,13 @@ graph TB
SMTP --> ES SMTP --> ES
DNS --> DS DNS --> DS
RAD --> RS RAD --> RS
EDGE --> RI
DC --> SP DC --> SP
DC --> ES DC --> ES
DC --> DS DC --> DS
DC --> RS DC --> RS
DC --> RI
DC --> CM DC --> CM
DC --> OS DC --> OS
DC --> MM DC --> MM
@@ -288,6 +309,7 @@ graph TB
SP --> API SP --> API
ES --> MAIL ES --> MAIL
ES --> DB ES --> DB
RI --> SP
CM -.-> SP CM -.-> SP
CM -.-> ES CM -.-> ES
@@ -303,6 +325,7 @@ graph TB
| **DNS Server** | `@push.rocks/smartdns` | Authoritative DNS with dynamic records and DKIM TXT auto-generation (Rust engine) | | **DNS Server** | `@push.rocks/smartdns` | Authoritative DNS with dynamic records and DKIM TXT auto-generation (Rust engine) |
| **SmartAcme** | `@push.rocks/smartacme` | ACME certificate management with per-domain dedup, concurrency control, and rate limiting | | **SmartAcme** | `@push.rocks/smartacme` | ACME certificate management with per-domain dedup, concurrency control, and rate limiting |
| **RADIUS Server** | `@push.rocks/smartradius` | Network authentication with MAB, VLAN assignment, and accounting | | **RADIUS Server** | `@push.rocks/smartradius` | Network authentication with MAB, VLAN assignment, and accounting |
| **RemoteIngress** | `@serve.zone/remoteingress` | Distributed edge tunneling with Rust data plane and TS management |
| **OpsServer** | `@api.global/typedserver` | Web dashboard + TypedRequest API for monitoring and management | | **OpsServer** | `@api.global/typedserver` | Web dashboard + TypedRequest API for monitoring and management |
| **MetricsManager** | `@push.rocks/smartmetrics` | Real-time metrics collection (CPU, memory, email, DNS, security) | | **MetricsManager** | `@push.rocks/smartmetrics` | Real-time metrics collection (CPU, memory, email, DNS, security) |
| **StorageManager** | built-in | Pluggable key-value storage (filesystem, custom, or in-memory) | | **StorageManager** | built-in | Pluggable key-value storage (filesystem, custom, or in-memory) |
@@ -312,19 +335,20 @@ graph TB
DcRouter acts purely as an **orchestrator** — it doesn't implement protocols itself. Instead, it wires together best-in-class packages for each protocol: DcRouter acts purely as an **orchestrator** — it doesn't implement protocols itself. Instead, it wires together best-in-class packages for each protocol:
1. **On `start()`**: DcRouter initializes OpsServer (port 3000), then spins up SmartProxy, smartmta, SmartDNS, and SmartRadius based on which configs are provided. 1. **On `start()`**: DcRouter initializes OpsServer (port 3000), then spins up SmartProxy, smartmta, SmartDNS, SmartRadius, and RemoteIngress based on which configs are provided.
2. **During operation**: Each service handles its own protocol independently. SmartProxy uses a Rust-powered engine for maximum throughput. smartmta uses a hybrid TypeScript + Rust architecture for reliable email delivery. SmartAcme v9 handles all certificate operations with built-in concurrency control and rate limiting. 2. **During operation**: Each service handles its own protocol independently. SmartProxy uses a Rust-powered engine for maximum throughput. smartmta uses a hybrid TypeScript + Rust architecture for reliable email delivery. RemoteIngress runs a Rust data plane for edge tunnel networking. SmartAcme v9 handles all certificate operations with built-in concurrency control and rate limiting.
3. **On `stop()`**: All services are gracefully shut down in parallel, including cleanup of HTTP agents and DNS clients. 3. **On `stop()`**: All services are gracefully shut down in parallel, including cleanup of HTTP agents and DNS clients.
### Rust-Powered Architecture ### Rust-Powered Architecture
DcRouter itself is a pure TypeScript orchestrator, but three of its core sub-components ship with **compiled Rust binaries** for performance-critical paths. At runtime each package detects the platform, unpacks the correct binary, and communicates with TypeScript over IPC/FFI — so you get the ergonomics of TypeScript with the throughput of native code. DcRouter itself is a pure TypeScript orchestrator, but several of its core sub-components ship with **compiled Rust binaries** for performance-critical paths. At runtime each package detects the platform, unpacks the correct binary, and communicates with TypeScript over IPC/FFI — so you get the ergonomics of TypeScript with the throughput of native code.
| Component | Rust Binary | What It Handles | | Component | Rust Binary | What It Handles |
|-----------|-------------|-----------------| |-----------|-------------|-----------------|
| **SmartProxy** | `smartproxy-bin` | All TCP/TLS/HTTP proxy networking, NFTables integration, connection metrics | | **SmartProxy** | `smartproxy-bin` | All TCP/TLS/HTTP proxy networking, NFTables integration, connection metrics |
| **smartmta** | `mailer-bin` | SMTP server + client, DKIM/SPF/DMARC, content scanning, IP reputation | | **smartmta** | `mailer-bin` | SMTP server + client, DKIM/SPF/DMARC, content scanning, IP reputation |
| **SmartDNS** | `smartdns-bin` | DNS server (UDP + DNS-over-HTTPS), DNSSEC, DNS client resolution | | **SmartDNS** | `smartdns-bin` | DNS server (UDP + DNS-over-HTTPS), DNSSEC, DNS client resolution |
| **RemoteIngress** | `remoteingress-bin` | Edge tunnel data plane, multiplexed streams, heartbeat management |
| **SmartRadius** | — | Pure TypeScript (no Rust component) | | **SmartRadius** | — | Pure TypeScript (no Rust component) |
## Configuration Reference ## Configuration Reference
@@ -333,6 +357,10 @@ DcRouter itself is a pure TypeScript orchestrator, but three of its core sub-com
```typescript ```typescript
interface IDcRouterOptions { interface IDcRouterOptions {
// ── Base ───────────────────────────────────────────────────────
/** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
baseDir?: string;
// ── Traffic Routing ──────────────────────────────────────────── // ── Traffic Routing ────────────────────────────────────────────
/** SmartProxy config for HTTP/HTTPS and TCP/SNI routing */ /** SmartProxy config for HTTP/HTTPS and TCP/SNI routing */
smartProxyConfig?: ISmartProxyOptions; smartProxyConfig?: ISmartProxyOptions;
@@ -376,6 +404,18 @@ interface IDcRouterOptions {
accounting?: { enabled: boolean; retentionDays?: number }; accounting?: { enabled: boolean; retentionDays?: number };
}; };
// ── Remote Ingress ─────────────────────────────────────────────
/** Remote Ingress hub for edge tunnel connections */
remoteIngressConfig?: {
enabled?: boolean; // default: false
tunnelPort?: number; // default: 8443
hubDomain?: string; // External hostname for connection tokens
tls?: {
certPath?: string;
keyPath?: string;
};
};
// ── TLS & Certificates ──────────────────────────────────────── // ── TLS & Certificates ────────────────────────────────────────
tls?: { tls?: {
contactEmail: string; contactEmail: string;
@@ -701,6 +741,107 @@ RADIUS is fully manageable at runtime via the OpsServer API:
- Session monitoring and forced disconnects - Session monitoring and forced disconnects
- Accounting summaries and statistics - Accounting summaries and statistics
## Remote Ingress
DcRouter can act as a **hub** for distributed edge nodes using [`@serve.zone/remoteingress`](https://code.foss.global/serve.zone/remoteingress). Edge nodes accept incoming traffic at remote locations and tunnel it back to the hub over a single multiplexed connection. This is ideal for scenarios where you need to accept traffic at multiple geographic locations but process it centrally.
### Enabling Remote Ingress
```typescript
const router = new DcRouter({
remoteIngressConfig: {
enabled: true,
tunnelPort: 8443,
hubDomain: 'hub.example.com', // Embedded in connection tokens
},
// Routes tagged with remoteIngress are auto-derived to edge listen ports
smartProxyConfig: {
routes: [
{
name: 'web-via-edge',
match: { domains: ['app.example.com'], ports: [443] },
action: {
type: 'forward',
targets: [{ host: '192.168.1.10', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' }
},
remoteIngress: { enabled: true } // Edges will listen on port 443
}
]
}
});
await router.start();
```
### Edge Registration
Edges are registered via the OpsServer API (or dashboard UI). Each edge gets a unique ID and secret:
```typescript
// Via TypedRequest API
const createReq = new TypedRequest<IReq_CreateRemoteIngress>(
'https://hub:3000/typedrequest', 'createRemoteIngress'
);
const { edge } = await createReq.fire({
identity,
name: 'edge-nyc-01',
autoDerivePorts: true,
tags: ['us-east'],
});
// edge.secret is returned only on creation — save it!
```
### Connection Tokens 🔑
Instead of configuring edges with four separate values (hubHost, hubPort, edgeId, secret), DcRouter can generate a single **connection token** — an opaque base64url string that encodes everything:
```typescript
// Via TypedRequest API
const tokenReq = new TypedRequest<IReq_GetRemoteIngressConnectionToken>(
'https://hub:3000/typedrequest', 'getRemoteIngressConnectionToken'
);
const { token } = await tokenReq.fire({ identity, edgeId: 'edge-uuid' });
// token = "eyJoIjoiaHViLmV4YW1wbGUuY29tIiwicCI6ODQ0MywiZSI6I..."
// On the edge side, just pass the token:
const edge = new RemoteIngressEdge({ token });
await edge.start();
```
The token is generated using `remoteingress.encodeConnectionToken()` and contains `{ hubHost, hubPort, edgeId, secret }`. The `hubHost` comes from `remoteIngressConfig.hubDomain` (or can be overridden per-request).
In the OpsServer dashboard, click **"Copy Token"** on any edge row to copy the connection token to your clipboard.
### Auto-Derived Ports
When routes have `remoteIngress: { enabled: true }`, edges with `autoDerivePorts: true` (default) automatically pick up those routes' ports. You can also use `edgeFilter` to restrict which edges get which ports:
```typescript
{
name: 'web-route',
match: { ports: [443] },
action: { /* ... */ },
remoteIngress: {
enabled: true,
edgeFilter: ['us-east', 'edge-uuid-123'] // Only edges with matching id or tags
}
}
```
### Dashboard Actions
The OpsServer Remote Ingress view provides:
| Action | Description |
|--------|-------------|
| **Create Edge Node** | Register a new edge with name, ports, tags |
| **Enable / Disable** | Toggle an edge on or off |
| **Edit** | Modify name, manual ports, auto-derive setting, tags |
| **Regenerate Secret** | Issue a new secret (invalidates the old one) |
| **Copy Token** | Generate and copy a base64url connection token to clipboard |
| **Delete** | Remove the edge registration |
## Certificate Management ## Certificate Management
DcRouter uses [`@push.rocks/smartacme`](https://code.foss.global/push.rocks/smartacme) v9 for ACME certificate provisioning. smartacme v9 brings significant improvements over previous versions: DcRouter uses [`@push.rocks/smartacme`](https://code.foss.global/push.rocks/smartacme) v9 for ACME certificate provisioning. smartacme v9 brings significant improvements over previous versions:
@@ -767,6 +908,7 @@ The OpsServer includes a **Certificates** view showing:
- Expiry dates and issuer information - Expiry dates and issuer information
- Backoff status for failed domains - Backoff status for failed domains
- One-click reprovisioning per domain - One-click reprovisioning per domain
- Certificate import and export
## Storage & Caching ## Storage & Caching
@@ -788,7 +930,7 @@ storage: {
// Simply omit the storage config // Simply omit the storage config
``` ```
Used for: TLS certificates, DKIM keys, email routes, bounce/suppression lists, IP reputation data, domain configs, cert backoff state. Used for: TLS certificates, DKIM keys, email routes, bounce/suppression lists, IP reputation data, domain configs, cert backoff state, remote ingress edge registrations.
### Cache Database ### Cache Database
@@ -874,7 +1016,8 @@ The OpsServer provides a web-based management interface served on port 3000. It'
| 📊 **Overview** | Real-time server stats, CPU/memory, connection counts, email throughput | | 📊 **Overview** | Real-time server stats, CPU/memory, connection counts, email throughput |
| 🌐 **Network** | Active connections, top IPs, throughput rates, SmartProxy metrics | | 🌐 **Network** | Active connections, top IPs, throughput rates, SmartProxy metrics |
| 📧 **Email** | Queue monitoring (queued/sent/failed), bounce records, security incidents | | 📧 **Email** | Queue monitoring (queued/sent/failed), bounce records, security incidents |
| 🔐 **Certificates** | Domain-centric certificate overview, status, backoff info, reprovisioning | | 🔐 **Certificates** | Domain-centric certificate overview, status, backoff info, reprovisioning, import/export |
| 🌍 **RemoteIngress** | Edge node management, connection status, token generation, enable/disable |
| 📜 **Logs** | Real-time log viewer with level filtering and search | | 📜 **Logs** | Real-time log viewer with level filtering and search |
| ⚙️ **Configuration** | Read-only view of current system configuration | | ⚙️ **Configuration** | Read-only view of current system configuration |
| 🛡️ **Security** | IP reputation, rate limit status, blocked connections | | 🛡️ **Security** | IP reputation, rate limit status, blocked connections |
@@ -906,6 +1049,18 @@ All management is done via TypedRequest over HTTP POST to `/typedrequest`:
'getCertificateOverview' // Domain-centric certificate status 'getCertificateOverview' // Domain-centric certificate status
'reprovisionCertificate' // Reprovision by route name (legacy) 'reprovisionCertificate' // Reprovision by route name (legacy)
'reprovisionCertificateDomain' // Reprovision by domain (preferred) 'reprovisionCertificateDomain' // Reprovision by domain (preferred)
'importCertificate' // Import a certificate
'exportCertificate' // Export a certificate
'deleteCertificate' // Delete a certificate
// Remote Ingress
'getRemoteIngresses' // List all edge registrations
'createRemoteIngress' // Register a new edge
'updateRemoteIngress' // Update edge settings
'deleteRemoteIngress' // Remove an edge
'regenerateRemoteIngressSecret' // Issue a new secret
'getRemoteIngressStatus' // Runtime status of all edges
'getRemoteIngressConnectionToken' // Generate a connection token for an edge
// Configuration (read-only) // Configuration (read-only)
'getConfiguration' // Current system config 'getConfiguration' // Current system config
@@ -957,6 +1112,8 @@ const router = new DcRouter(options: IDcRouterOptions);
| `emailServer` | `UnifiedEmailServer` | Email server instance (from smartmta) | | `emailServer` | `UnifiedEmailServer` | Email server instance (from smartmta) |
| `dnsServer` | `DnsServer` | DNS server instance | | `dnsServer` | `DnsServer` | DNS server instance |
| `radiusServer` | `RadiusServer` | RADIUS server instance | | `radiusServer` | `RadiusServer` | RADIUS server instance |
| `remoteIngressManager` | `RemoteIngressManager` | Edge registration CRUD manager |
| `tunnelManager` | `TunnelManager` | Tunnel lifecycle and status manager |
| `storageManager` | `StorageManager` | Storage backend | | `storageManager` | `StorageManager` | Storage backend |
| `opsServer` | `OpsServer` | OpsServer/dashboard instance | | `opsServer` | `OpsServer` | OpsServer/dashboard instance |
| `metricsManager` | `MetricsManager` | Metrics collector | | `metricsManager` | `MetricsManager` | Metrics collector |
@@ -1000,7 +1157,7 @@ import { data, requests } from '@serve.zone/dcrouter/interfaces';
DcRouter includes a comprehensive test suite covering all system components: DcRouter includes a comprehensive test suite covering all system components:
```bash ```bash
# Run all tests (10 files, 73 tests) # Run all tests
pnpm test pnpm test
# Run a specific test file # Run a specific test file

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '6.11.0', version: '6.12.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

146
ts/readme.md Normal file
View File

@@ -0,0 +1,146 @@
# @serve.zone/dcrouter
The core DcRouter package — a unified datacenter gateway orchestrator. 🚀
This is the main entry point for DcRouter. It provides the `DcRouter` class that wires together SmartProxy, smartmta, SmartDNS, SmartRadius, RemoteIngress, and the OpsServer dashboard into a single cohesive service.
## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## Installation
```bash
pnpm add @serve.zone/dcrouter
```
## Usage
```typescript
import { DcRouter } from '@serve.zone/dcrouter';
const router = new DcRouter({
smartProxyConfig: {
routes: [
{
name: 'web-app',
match: { domains: ['example.com'], ports: [443] },
action: {
type: 'forward',
targets: [{ host: '192.168.1.10', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' }
}
}
],
acme: { email: 'admin@example.com', enabled: true, useProduction: true }
}
});
await router.start();
// OpsServer dashboard at http://localhost:3000
// Graceful shutdown
await router.stop();
```
## Module Structure
```
ts/
├── index.ts # Main exports (DcRouter, re-exported smartmta types)
├── classes.dcrouter.ts # DcRouter orchestrator class + IDcRouterOptions
├── classes.cert-provision-scheduler.ts # Per-domain cert backoff scheduler
├── classes.storage-cert-manager.ts # SmartAcme cert manager backed by StorageManager
├── logger.ts # Structured logging utility
├── paths.ts # Centralized data directory paths
├── plugins.ts # All dependency imports
├── cache/ # Cache database (smartdata + LocalTsmDb)
│ ├── classes.cachedb.ts # CacheDb singleton
│ ├── classes.cachecleaner.ts # TTL-based cleanup
│ └── documents/ # Cached document models
├── config/ # Configuration utilities
├── errors/ # Error classes and retry logic
├── monitoring/ # MetricsManager (SmartMetrics integration)
├── opsserver/ # OpsServer dashboard + API handlers
│ ├── classes.opsserver.ts # HTTP server + TypedRouter setup
│ └── handlers/ # TypedRequest handlers by domain
│ ├── admin.handler.ts # Auth (login/logout/verify)
│ ├── stats.handler.ts # Statistics + health
│ ├── config.handler.ts # Configuration (read-only)
│ ├── logs.handler.ts # Log retrieval
│ ├── email.handler.ts # Email operations
│ ├── certificate.handler.ts # Certificate management
│ ├── radius.handler.ts # RADIUS management
│ └── remoteingress.handler.ts # Remote ingress edge + token management
├── radius/ # RADIUS server integration
├── remoteingress/ # Remote ingress hub integration
│ ├── classes.remoteingress-manager.ts # Edge CRUD + port derivation
│ └── classes.tunnel-manager.ts # Rust hub lifecycle + status tracking
├── security/ # Security utilities
├── sms/ # SMS integration
└── storage/ # StorageManager (filesystem/custom/memory)
```
## Exports
```typescript
// Main class
export { DcRouter, IDcRouterOptions } from './classes.dcrouter.js';
// Re-exported from smartmta
export { UnifiedEmailServer } from '@push.rocks/smartmta';
export type { IUnifiedEmailServerOptions, IEmailRoute, IEmailDomainConfig } from '@push.rocks/smartmta';
// RADIUS
export { RadiusServer, IRadiusServerConfig } from './radius/index.js';
// Remote Ingress
export { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
```
## Key Classes
### `DcRouter`
The central orchestrator. Accepts `IDcRouterOptions` and manages the lifecycle of all sub-services:
| Config Section | Service Started | Package |
|----------------|----------------|---------|
| `smartProxyConfig` | SmartProxy (HTTP/HTTPS/TCP/SNI) | `@push.rocks/smartproxy` |
| `emailConfig` | UnifiedEmailServer (SMTP) | `@push.rocks/smartmta` |
| `dnsNsDomains` + `dnsScopes` | DnsServer (UDP + DoH) | `@push.rocks/smartdns` |
| `radiusConfig` | RadiusServer (auth + accounting) | `@push.rocks/smartradius` |
| `remoteIngressConfig` | RemoteIngressManager + TunnelManager | `@serve.zone/remoteingress` |
| `tls` + `dnsChallenge` | SmartAcme (ACME cert provisioning) | `@push.rocks/smartacme` |
| `cacheConfig` | CacheDb (embedded MongoDB) | `@push.rocks/smartdata` |
| *(always)* | OpsServer (dashboard + API) | `@api.global/typedserver` |
| *(always)* | MetricsManager | `@push.rocks/smartmetrics` |
### `RemoteIngressManager`
Manages CRUD for remote ingress edge registrations. Persists edges via StorageManager. Provides port derivation from routes tagged with `remoteIngress.enabled`.
### `TunnelManager`
Manages the Rust-based RemoteIngressHub lifecycle. Syncs allowed edges, tracks connection status, and exposes edge statuses (connected, publicIp, activeTunnels, lastHeartbeat).
## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../LICENSE) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
### Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

@@ -82,6 +82,14 @@ interface IIdentity {
| `INetworkMetrics` | Bandwidth, connection counts, top endpoints | | `INetworkMetrics` | Bandwidth, connection counts, top endpoints |
| `ILogEntry` | Timestamp, level, category, message, metadata | | `ILogEntry` | Timestamp, level, category, message, metadata |
#### Remote Ingress Interfaces
| Interface | Description |
|-----------|-------------|
| `IRemoteIngress` | Edge registration: id, name, secret, listenPorts, enabled, autoDerivePorts, tags |
| `IRemoteIngressStatus` | Runtime status: connected, publicIp, activeTunnels, lastHeartbeat |
| `IRouteRemoteIngress` | Route-level config: enabled flag and optional edgeFilter |
| `IDcRouterRouteConfig` | Extended SmartProxy route config with optional `remoteIngress` property |
### Request Interfaces (`requests`) ### Request Interfaces (`requests`)
TypedRequest interfaces for the OpsServer API, organized by domain: TypedRequest interfaces for the OpsServer API, organized by domain:
@@ -134,6 +142,9 @@ TypedRequest interfaces for the OpsServer API, organized by domain:
| `IReq_GetCertificateOverview` | `getCertificateOverview` | Domain-centric certificate status | | `IReq_GetCertificateOverview` | `getCertificateOverview` | Domain-centric certificate status |
| `IReq_ReprovisionCertificate` | `reprovisionCertificate` | Reprovision by route name (legacy) | | `IReq_ReprovisionCertificate` | `reprovisionCertificate` | Reprovision by route name (legacy) |
| `IReq_ReprovisionCertificateDomain` | `reprovisionCertificateDomain` | Reprovision by domain (preferred) | | `IReq_ReprovisionCertificateDomain` | `reprovisionCertificateDomain` | Reprovision by domain (preferred) |
| `IReq_ImportCertificate` | `importCertificate` | Import a certificate |
| `IReq_ExportCertificate` | `exportCertificate` | Export a certificate |
| `IReq_DeleteCertificate` | `deleteCertificate` | Delete a certificate |
#### Certificate Types #### Certificate Types
```typescript ```typescript
@@ -159,6 +170,17 @@ interface ICertificateInfo {
} }
``` ```
#### 🌍 Remote Ingress
| Interface | Method | Description |
|-----------|--------|-------------|
| `IReq_CreateRemoteIngress` | `createRemoteIngress` | Register a new edge node |
| `IReq_DeleteRemoteIngress` | `deleteRemoteIngress` | Remove an edge registration |
| `IReq_UpdateRemoteIngress` | `updateRemoteIngress` | Update edge settings |
| `IReq_RegenerateRemoteIngressSecret` | `regenerateRemoteIngressSecret` | Issue a new secret |
| `IReq_GetRemoteIngresses` | `getRemoteIngresses` | List all edge registrations |
| `IReq_GetRemoteIngressStatus` | `getRemoteIngressStatus` | Runtime status of all edges |
| `IReq_GetRemoteIngressConnectionToken` | `getRemoteIngressConnectionToken` | Generate a connection token |
#### 📡 RADIUS #### 📡 RADIUS
| Interface | Method | Description | | Interface | Method | Description |
|-----------|--------|-------------| |-----------|--------|-------------|
@@ -183,7 +205,7 @@ import { data, requests } from '@serve.zone/dcrouter-interfaces';
// 1. Login // 1. Login
const loginClient = new typedrequest.TypedRequest<requests.IReq_AdminLoginWithUsernameAndPassword>( const loginClient = new typedrequest.TypedRequest<requests.IReq_AdminLoginWithUsernameAndPassword>(
'https://your-dcrouter:3000/typedrequest', 'https://your-dcrouter:3000/typedrequest',
'adminLogin' 'adminLoginWithUsernameAndPassword'
); );
const loginResponse = await loginClient.fire({ const loginResponse = await loginClient.fire({
@@ -199,10 +221,8 @@ const metricsClient = new typedrequest.TypedRequest<requests.IReq_GetCombinedMet
); );
const metrics = await metricsClient.fire({ identity }); const metrics = await metricsClient.fire({ identity });
console.log('Server:', metrics.serverStats); console.log('Server:', metrics.metrics.server);
console.log('Email:', metrics.emailStats); console.log('Email:', metrics.metrics.email);
console.log('DNS:', metrics.dnsStats);
console.log('Security:', metrics.securityMetrics);
// 3. Check certificate status // 3. Check certificate status
const certClient = new typedrequest.TypedRequest<requests.IReq_GetCertificateOverview>( const certClient = new typedrequest.TypedRequest<requests.IReq_GetCertificateOverview>(
@@ -213,14 +233,23 @@ const certClient = new typedrequest.TypedRequest<requests.IReq_GetCertificateOve
const certs = await certClient.fire({ identity }); const certs = await certClient.fire({ identity });
console.log(`Certificates: ${certs.summary.valid} valid, ${certs.summary.failed} failed`); console.log(`Certificates: ${certs.summary.valid} valid, ${certs.summary.failed} failed`);
// 4. Check email queues // 4. List remote ingress edges
const queueClient = new typedrequest.TypedRequest<requests.IReq_GetQueuedEmails>( const edgesClient = new typedrequest.TypedRequest<requests.IReq_GetRemoteIngresses>(
'https://your-dcrouter:3000/typedrequest', 'https://your-dcrouter:3000/typedrequest',
'getQueuedEmails' 'getRemoteIngresses'
); );
const queued = await queueClient.fire({ identity }); const edges = await edgesClient.fire({ identity });
console.log('Queued emails:', queued.emails.length); console.log('Registered edges:', edges.edges.length);
// 5. Generate a connection token for an edge
const tokenClient = new typedrequest.TypedRequest<requests.IReq_GetRemoteIngressConnectionToken>(
'https://your-dcrouter:3000/typedrequest',
'getRemoteIngressConnectionToken'
);
const tokenResponse = await tokenClient.fire({ identity, edgeId: edges.edges[0].id });
console.log('Connection token:', tokenResponse.token);
``` ```
## License and Legal Information ## License and Legal Information

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '6.11.0', version: '6.12.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -200,7 +200,7 @@ export interface IRemoteIngressState {
edges: interfaces.data.IRemoteIngress[]; edges: interfaces.data.IRemoteIngress[];
statuses: interfaces.data.IRemoteIngressStatus[]; statuses: interfaces.data.IRemoteIngressStatus[];
selectedEdgeId: string | null; selectedEdgeId: string | null;
newEdgeSecret: string | null; newEdgeId: string | null;
isLoading: boolean; isLoading: boolean;
error: string | null; error: string | null;
lastUpdated: number; lastUpdated: number;
@@ -212,7 +212,7 @@ export const remoteIngressStatePart = await appState.getStatePart<IRemoteIngress
edges: [], edges: [],
statuses: [], statuses: [],
selectedEdgeId: null, selectedEdgeId: null,
newEdgeSecret: null, newEdgeId: null,
isLoading: false, isLoading: false,
error: null, error: null,
lastUpdated: 0, lastUpdated: 0,
@@ -928,11 +928,12 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
}); });
if (response.success) { if (response.success) {
// Refresh the list and store the new secret for display // Refresh the list
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null); await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
return { return {
...statePartArg.getState(), ...statePartArg.getState(),
newEdgeSecret: response.edge.secret, newEdgeId: response.edge.id,
}; };
} }
@@ -1023,7 +1024,7 @@ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.create
if (response.success) { if (response.success) {
return { return {
...currentState, ...currentState,
newEdgeSecret: response.secret, newEdgeId: edgeId,
}; };
} }
@@ -1037,11 +1038,11 @@ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.create
} }
); );
export const clearNewEdgeSecretAction = remoteIngressStatePart.createAction( export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
async (statePartArg) => { async (statePartArg) => {
return { return {
...statePartArg.getState(), ...statePartArg.getState(),
newEdgeSecret: null, newEdgeId: null,
}; };
} }
); );

View File

@@ -382,7 +382,7 @@ export class OpsViewCertificates extends DeesElement {
}, },
{ {
name: 'View Details', name: 'View Details',
iconName: 'lucide:Search', iconName: 'fa:magnifyingGlass',
type: ['doubleClick', 'contextmenu'], type: ['doubleClick', 'contextmenu'],
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => { actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
const cert = actionData.item; const cert = actionData.item;

View File

@@ -287,7 +287,7 @@ export class OpsViewNetwork extends DeesElement {
.dataActions=${[ .dataActions=${[
{ {
name: 'View Details', name: 'View Details',
iconName: 'lucide:Search', iconName: 'fa:magnifyingGlass',
type: ['inRow', 'doubleClick', 'contextmenu'], type: ['inRow', 'doubleClick', 'contextmenu'],
actionFunc: async (actionData) => { actionFunc: async (actionData) => {
await this.showRequestDetails(actionData.item); await this.showRequestDetails(actionData.item);
@@ -435,7 +435,7 @@ export class OpsViewNetwork extends DeesElement {
actions: [ actions: [
{ {
name: 'View Details', name: 'View Details',
iconName: 'lucide:Search', iconName: 'fa:magnifyingGlass',
action: async () => { action: async () => {
}, },
}, },

View File

@@ -176,13 +176,39 @@ export class OpsViewRemoteIngress extends DeesElement {
return html` return html`
<ops-sectionheading>Remote Ingress</ops-sectionheading> <ops-sectionheading>Remote Ingress</ops-sectionheading>
${this.riState.newEdgeSecret ? html` ${this.riState.newEdgeId ? html`
<div class="secretDialog"> <div class="secretDialog">
<strong>Edge Secret (copy now - shown only once):</strong> <strong>Edge created successfully!</strong>
<code>${this.riState.newEdgeSecret}</code> <div class="warning">Copy the connection token now. Use it with edge.start({ token: '...' }).</div>
<div class="warning">This secret will not be shown again. Save it securely.</div>
<dees-button <dees-button
@click=${() => appstate.remoteIngressStatePart.dispatchAction(appstate.clearNewEdgeSecretAction, null)} @click=${async () => {
const { DeesToast } = await import('@design.estate/dees-catalog');
try {
const response = await appstate.fetchConnectionToken(this.riState.newEdgeId);
if (response.success && response.token) {
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
await navigator.clipboard.writeText(response.token);
} else {
const textarea = document.createElement('textarea');
textarea.value = response.token;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
DeesToast.show({ message: 'Connection token copied!', type: 'success', duration: 3000 });
} else {
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
}
} catch (err) {
DeesToast.show({ message: `Failed: ${err.message}`, type: 'error', duration: 4000 });
}
}}
>Copy Connection Token</dees-button>
<dees-button
@click=${() => appstate.remoteIngressStatePart.dispatchAction(appstate.clearNewEdgeIdAction, null)}
>Dismiss</dees-button> >Dismiss</dees-button>
</div> </div>
` : ''} ` : ''}
@@ -348,7 +374,7 @@ export class OpsViewRemoteIngress extends DeesElement {
}, },
{ {
name: 'Copy Token', name: 'Copy Token',
iconName: 'lucide:clipboard-copy', iconName: 'lucide:ClipboardCopy',
type: ['inRow', 'contextmenu'] as any, type: ['inRow', 'contextmenu'] as any,
actionFunc: async (actionData: any) => { actionFunc: async (actionData: any) => {
const edge = actionData.item as interfaces.data.IRemoteIngress; const edge = actionData.item as interfaces.data.IRemoteIngress;
@@ -356,7 +382,19 @@ export class OpsViewRemoteIngress extends DeesElement {
try { try {
const response = await appstate.fetchConnectionToken(edge.id); const response = await appstate.fetchConnectionToken(edge.id);
if (response.success && response.token) { if (response.success && response.token) {
// Use clipboard API with fallback for non-HTTPS contexts
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
await navigator.clipboard.writeText(response.token); await navigator.clipboard.writeText(response.token);
} else {
const textarea = document.createElement('textarea');
textarea.value = response.token;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
DeesToast.show({ message: `Connection token copied for ${edge.name}`, type: 'success', duration: 3000 }); DeesToast.show({ message: `Connection token copied for ${edge.name}`, type: 'success', duration: 3000 });
} else { } else {
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 }); DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });

View File

@@ -40,6 +40,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- Expiry date monitoring and alerts - Expiry date monitoring and alerts
- Per-domain backoff status for failed provisions - Per-domain backoff status for failed provisions
- One-click reprovisioning per domain - One-click reprovisioning per domain
- Certificate import, export, and deletion
### 🌍 Remote Ingress Management
- Edge node registration with name, ports, and tags
- Real-time connection status (connected/disconnected/disabled)
- Public IP and active tunnel count per edge
- Auto-derived port display with manual/derived breakdown
- **Connection token generation** — one-click "Copy Token" for easy edge provisioning
- Enable/disable, edit, secret regeneration, and delete actions
### 📜 Log Viewer ### 📜 Log Viewer
- Real-time log streaming - Real-time log streaming
@@ -85,6 +94,7 @@ ts_web/
├── ops-view-network.ts # Network monitoring ├── ops-view-network.ts # Network monitoring
├── ops-view-emails.ts # Email queue management ├── ops-view-emails.ts # Email queue management
├── ops-view-certificates.ts # Certificate overview & reprovisioning ├── ops-view-certificates.ts # Certificate overview & reprovisioning
├── ops-view-remoteingress.ts # Remote ingress edge management
├── ops-view-logs.ts # Log viewer ├── ops-view-logs.ts # Log viewer
├── ops-view-config.ts # Configuration display ├── ops-view-config.ts # Configuration display
├── ops-view-security.ts # Security dashboard ├── ops-view-security.ts # Security dashboard
@@ -106,6 +116,8 @@ The app uses `@push.rocks/smartstate` with multiple state parts:
| `logStatePart` | Soft | Recent logs, streaming status, filters | | `logStatePart` | Soft | Recent logs, streaming status, filters |
| `networkStatePart` | Soft | Connections, IPs, throughput rates | | `networkStatePart` | Soft | Connections, IPs, throughput rates |
| `emailOpsStatePart` | Soft | Email queues, bounces, suppression list | | `emailOpsStatePart` | Soft | Email queues, bounces, suppression list |
| `certificateStatePart` | Soft | Certificate list, summary, loading state |
| `remoteIngressStatePart` | Soft | Edge list, statuses, new edge secret |
### Actions ### Actions
@@ -128,6 +140,23 @@ fetchSecurityIncidentsAction() // Security events
fetchBounceRecordsAction() // Bounce records fetchBounceRecordsAction() // Bounce records
resendEmailAction(emailId) // Re-queue failed email resendEmailAction(emailId) // Re-queue failed email
removeFromSuppressionAction(email) // Remove from suppression list removeFromSuppressionAction(email) // Remove from suppression list
// Certificates
fetchCertificateOverviewAction() // All certificates with summary
reprovisionCertificateAction(domain) // Reprovision a certificate
deleteCertificateAction(domain) // Delete a certificate
importCertificateAction(cert) // Import a certificate
fetchCertificateExport(domain) // Export (standalone function)
// Remote Ingress
fetchRemoteIngressAction() // Edges + statuses
createRemoteIngressAction(data) // Create new edge
updateRemoteIngressAction(data) // Update edge settings
deleteRemoteIngressAction(id) // Remove edge
regenerateRemoteIngressSecretAction(id) // New secret
toggleRemoteIngressAction(id, enabled) // Enable/disable
clearNewEdgeSecretAction() // Dismiss secret banner
fetchConnectionToken(edgeId) // Get connection token (standalone function)
``` ```
### Client-Side Routing ### Client-Side Routing
@@ -141,6 +170,7 @@ removeFromSuppressionAction(email) // Remove from suppression list
/emails/failed → Failed emails /emails/failed → Failed emails
/emails/security → Security incidents /emails/security → Security incidents
/certificates → Certificate management /certificates → Certificate management
/remoteingress → Remote ingress edge management
/logs → Log viewer /logs → Log viewer
/configuration → System configuration /configuration → System configuration
/security → Security dashboard /security → Security dashboard