2025-05-07 23:04:54 +00:00
# Implementation Hints and Learnings
2026-02-11 16:32:49 +00:00
## smartmta Migration (2026-02-11)
### Overview
dcrouter's custom MTA code (~27,149 lines / 68 files in `ts/mail/` + `ts/deliverability/` ) has been replaced with `@push.rocks/smartmta` v5.2.1, a TypeScript+Rust hybrid MTA. dcrouter is now an orchestrator that wires together SmartProxy, smartmta, smartdns, smartradius, and OpsServer.
### Architecture
- **No socket-handler mode** — smartmta's Rust SMTP server binds its own ports directly
- **SmartProxy forward mode only** — external email ports forwarded to internal ports where smartmta listens
- Email traffic flow: External Port → SmartProxy → Internal Port → smartmta UnifiedEmailServer
### Key API Differences (smartmta vs old custom MTA)
- `updateEmailRoutes()` instead of `updateRoutes()`
- `dkimCreator` is public (no need for `(this.emailServer as any).dkimCreator` )
- `bounceManager` is private, but exposed via public methods:
- `emailServer.getSuppressionList()`
- `emailServer.getHardBouncedAddresses()`
- `emailServer.getBounceHistory(email)`
- `emailServer.removeFromSuppressionList(email)`
- `Email` class imported from `@push.rocks/smartmta`
- `IAttachment` type accessed via `Core` namespace: `import { type Core } from '@push.rocks/smartmta'; type IAttachment = Core.IAttachment;`
### Deleted Directories
- `ts/mail/` (60 files) — replaced by smartmta
- `ts/deliverability/` (3 files) — IPWarmupManager/SenderReputationMonitor will move to smartmta
- `ts/errors/email.errors.ts` , `ts/errors/mta.errors.ts` — smartmta has its own errors
- `ts/cache/documents/classes.cached.bounce.ts` , `classes.cached.suppression.ts` , `classes.cached.dkim.ts` — smartmta handles its own persistence
### Remaining Cache Documents
- `CachedEmail` — kept (dcrouter-level queue persistence)
- `CachedIPReputation` — kept (dcrouter-level IP reputation caching)
### Dependencies Removed
mailauth, mailparser, @types/mailparser , ip, @push .rocks/smartmail, @push .rocks/smartrule, node-forge
### Pre-existing Test Failures (not caused by migration)
- `test/test.jwt-auth.ts` — `response.text is not a function` (webrequest compatibility issue)
- `test/test.opsserver-api.ts` — same webrequest issue, timeouts
### smartmta Location
Source at `../../push.rocks/smartmta` , release with `gitzone commit -ypbrt`
## Dependency Upgrade (2026-02-11)
### SmartProxy v23.1.2 Route Validation
- SmartProxy 23.1.2 enforces stricter route validation
- Forward actions MUST use `targets` (array) instead of `target` (singular)
2026-02-12 14:20:42 +00:00
- Test configurations that call `DcRouter.start()` need `cacheConfig: { enabled: false }` to avoid starting a real MongoDB process in tests
2026-02-11 16:32:49 +00:00
``` typescript
// WRONG - will fail validation
action : { type : 'forward' , target : { host : 'localhost' , port : 10025 } }
// CORRECT
action : { type : 'forward' , targets : [ { host : 'localhost' , port : 10025 } ] }
```
**Files Fixed: **
- `ts/classes.dcrouter.ts` - `generateEmailRoutes()` method
- `test/test.dcrouter.email.ts` - Updated assertions and added `cacheConfig: { enabled: false }`
2026-02-10 14:41:19 +00:00
## Dependency Upgrade (2026-02-10)
### SmartProxy v23.1.0 Upgrade
- `@push.rocks/smartproxy` : 22.4.2 → 23.1.0
**Key Changes: **
- Rust-based proxy components for improved performance
- Rust binary runs as separate process via IPC
- `getStatistics()` now returns `Promise<any>` (was synchronous)
- nftables-proxy removed (not used by dcrouter)
**Code Changes Required: **
``` typescript
// Old (synchronous)
const proxyStats = this . dcRouter . smartProxy . getStatistics ( ) ;
// New (async)
const proxyStats = await this . dcRouter . smartProxy . getStatistics ( ) ;
```
**Files Modified: **
- `ts/monitoring/classes.metricsmanager.ts` - Added `await` to `getStatistics()` call
2026-02-02 00:36:19 +00:00
## Dependency Upgrade (2026-02-01)
### Major Upgrades Completed
- `@api.global/typedserver` : 3.0.80 → 8.3.0
- `@api.global/typedsocket` : 3.1.1 → 4.1.0
- `@apiclient.xyz/cloudflare` : 6.4.3 → 7.1.0
- `@design.estate/dees-catalog` : 1.12.4 → 3.41.4
- `@push.rocks/smartpath` : 5.1.0 → 6.0.0
- `@push.rocks/smartproxy` : 19.6.17 → 22.4.2
- `@push.rocks/smartrequest` : 2.1.0 → 5.0.1
- `uuid` : 11.1.0 → 13.0.0
### Breaking Changes Fixed
1. **SmartProxy v22 ** : `target` → `targets` (array)
```typescript
// Old
action: { type: 'forward', target: { host: 'x', port: 25 } }
// New
action: { type: 'forward', targets: [{ host: 'x', port: 25 }] }
` ``
2. **SmartRequest v5**: ` SmartRequestClient` → ` SmartRequest`, ` .body` → ` .json()`
` ``typescript
// Old
const resp = await plugins.smartrequest.SmartRequestClient.create()...post();
const json = resp.body;
// New
const resp = await plugins.smartrequest.SmartRequest.create()...post();
const json = await resp.json();
` ``
3. **dees-catalog v3**: Icon naming changed to library-prefixed format
` ``typescript
// Old (deprecated but supported)
<dees-icon iconFA="check"></dees-icon>
// New
<dees-icon icon="fa:check"></dees-icon>
<dees-icon icon="lucide:menu"></dees-icon>
` ``
### TC39 Decorators
- ts_web components updated to use ` accessor` keyword for ` @state ()` decorators
- Required for TC39 standard decorator support
### tswatch Configuration
The project now uses tswatch for development:
` ``bash
pnpm run watch
` ``
2026-03-26 07:10:59 +00:00
Configuration in ` .smartconfig.json`:
2026-02-02 00:36:19 +00:00
` ``json
{
"@git.zone/tswatch": {
"watchers": [{
"name": "dcrouter-dev",
"watch": ["ts/**/*.ts", "ts_*/**/*.ts", "test_watch/devserver.ts"],
"command": "pnpm run build && tsrun test_watch/devserver.ts",
"restart": true,
"debounce": 500,
"runOnStart": true
}]
}
}
` ``
2026-02-01 19:21:37 +00:00
## RADIUS Server Integration (2026-02-01)
### Overview
DcRouter now supports RADIUS server functionality for network authentication via ` @push .rocks/smartradius`.
### Key Features
- **MAC Authentication Bypass (MAB)** - Authenticate network devices based on MAC address
- **VLAN Assignment** - Assign VLANs based on MAC address or OUI patterns
- **RADIUS Accounting** - Track sessions, data usage, and billing
### Configuration Example
` ``typescript
const dcRouter = new DcRouter({
radiusConfig: {
authPort: 1812, // Authentication port (default)
acctPort: 1813, // Accounting port (default)
clients: [
{
name: 'switch-1',
ipRange: '192.168.1.0/24',
secret: 'shared-secret',
enabled: true
}
],
vlanAssignment: {
defaultVlan: 100, // VLAN for unknown MACs
allowUnknownMacs: true,
mappings: [
{ mac: '00:11:22:33:44:55', vlan: 10, enabled: true },
{ mac: '00:11:22', vlan: 20, enabled: true } // OUI pattern
]
},
accounting: {
enabled: true,
retentionDays: 30
}
}
});
` ``
### Components
- ` RadiusServer` - Main server wrapping smartradius
- ` VlanManager` - MAC-to-VLAN mapping with OUI pattern support
- ` AccountingManager` - Session tracking and billing data
### OpsServer API Endpoints
- ` getRadiusClients` / ` setRadiusClient` / ` removeRadiusClient` - Client management
- ` getVlanMappings` / ` setVlanMapping` / ` removeVlanMapping` - VLAN mappings
- ` testVlanAssignment` - Test what VLAN a MAC would get
- ` getRadiusSessions` / ` disconnectRadiusSession` - Session management
- ` getRadiusStatistics` / ` getRadiusAccountingSummary` - Statistics
### Files
- ` ts/radius/` - RADIUS module
- ` ts/opsserver/handlers/radius.handler.ts` - OpsServer handler
- ` ts_interfaces/requests/radius.ts` - TypedRequest interfaces
2026-02-01 18:10:30 +00:00
## Test Fix: test.dcrouter.email.ts (2026-02-01)
### Issue
The test ` DcRouter class - Custom email storage path` was failing with "domainConfigs is not iterable".
### Root Cause
The test was using outdated email config properties:
- Used ` domainRules: []` (non-existent property)
- Used ` defaultMode` (non-existent property)
- Missing required ` domains: []` property
- Missing required ` routes: []` property
- Referenced ` router.unifiedEmailServer` instead of ` router.emailServer`
### Fix
Updated the test to use the correct ` IUnifiedEmailServerOptions` interface properties:
` ``typescript
const emailConfig: IEmailConfig = {
ports: [2525],
hostname: 'mail.example.com',
domains: [], // Required: domain configurations
routes: [] // Required: email routing rules
};
` ``
And fixed the property name:
` ``typescript
expect(router.emailServer).toBeTruthy(); // Not unifiedEmailServer
` ``
### Key Learning
When using ` IUnifiedEmailServerOptions` (aliased as ` IEmailConfig` in some tests):
- ` domains: IEmailDomainConfig[]` is required (array of domain configs)
- ` routes: IEmailRoute[]` is required (email routing rules)
- Access the email server via ` dcRouter.emailServer` not ` dcRouter.unifiedEmailServer`
2025-06-23 00:19:47 +00:00
## Network Metrics Implementation (2025-06-23)
### SmartProxy Metrics API Integration
- Updated to use new SmartProxy metrics API (v19.6.7)
- Use ` getMetrics()` for detailed metrics with grouped methods:
` ``typescript
const metrics = smartProxy.getMetrics();
metrics.connections.active() // Current active connections
metrics.throughput.instant() // Real-time throughput {in, out}
metrics.connections.topIPs(10) // Top 10 IPs by connection count
` ``
- Use ` getStatistics()` for basic stats
### Network Traffic Display
- All throughput values shown in bits per second (kbit/s, Mbit/s, Gbit/s)
- Conversion: ` bytesPerSecond * 8 / 1000000` for Mbps
- Network graph shows separate lines for inbound (green) and outbound (purple)
- Throughput tiles and graph use same data source for consistency
### Requests/sec vs Connections
- Requests/sec shows HTTP request counts (derived from connections)
- Single connection can handle multiple requests
- Current implementation tracks connections, not individual requests
- Trend line shows historical request counts, not throughput
2025-05-31 12:53:29 +00:00
## DKIM Implementation Status (2025-05-30)
2026-02-11 16:32:49 +00:00
**Note:** DKIM is now handled by ` @push .rocks/smartmta`. The ` dkimCreator` is a public property on ` UnifiedEmailServer`.
2025-05-31 12:53:29 +00:00
2025-05-07 23:04:54 +00:00
## SmartProxy Usage
2025-05-16 15:50:46 +00:00
### New Route-Based Architecture (v18+)
- SmartProxy now uses a route-based configuration system
- Routes define match criteria and actions instead of simple port-to-port forwarding
- All traffic types (HTTP, HTTPS, TCP, WebSocket) are configured through routes
2025-05-07 23:04:54 +00:00
` ``typescript
2025-05-16 15:50:46 +00:00
// NEW: Route-based SmartProxy configuration
2025-05-07 23:04:54 +00:00
const smartProxy = new plugins.smartproxy.SmartProxy({
2025-05-16 15:50:46 +00:00
routes: [
{
name: 'https-traffic',
match: {
ports: 443,
domains: ['example.com', '*.example.com']
},
action: {
type: 'forward',
target: {
host: 'backend.server.com',
port: 8080
}
},
tls: {
mode: 'terminate',
certificate: 'auto'
}
}
],
defaults: {
target: {
host: 'fallback.server.com',
port: 8080
}
},
2025-05-07 23:04:54 +00:00
acme: {
2025-05-16 15:50:46 +00:00
accountEmail: 'admin@example.com',
2025-05-07 23:04:54 +00:00
enabled: true,
2025-05-16 15:50:46 +00:00
useProduction: true
}
2025-05-07 23:04:54 +00:00
});
` ``
2025-05-16 15:50:46 +00:00
### Migration from Old to New
` ``typescript
// OLD configuration style (deprecated)
{
fromPort: 443,
toPort: 8080,
targetIP: 'backend.server.com',
domainConfigs: [...]
}
// NEW route-based style
{
routes: [{
name: 'main-route',
match: { ports: 443 },
action: {
type: 'forward',
target: { host: 'backend.server.com', port: 8080 }
}
}]
}
` ``
### Direct Component Usage
- Use SmartProxy components directly instead of creating your own wrappers
- SmartProxy already includes Port80Handler and NetworkProxy functionality
- When using SmartProxy, configure it directly rather than instantiating Port80Handler or NetworkProxy separately
2025-05-07 23:04:54 +00:00
### Certificate Management
- SmartProxy has built-in ACME certificate management
- Configure it in the ` acme` property of SmartProxy options
- Use ` accountEmail` (not ` email`) for the ACME contact email
- SmartProxy handles both HTTP-01 challenges and certificate application automatically
## qenv Usage
### Direct Usage
- Use qenv directly instead of creating environment variable wrappers
- Instantiate qenv with appropriate basePath and nogitPath:
` ``typescript
const qenv = new plugins.qenv.Qenv('./', '.nogit/');
const value = await qenv.getEnvVarOnDemand('ENV_VAR_NAME');
` ``
## TypeScript Interfaces
### SmartProxy Interfaces
- Always check the interfaces from the node_modules to ensure correct property names
2025-05-16 15:50:46 +00:00
- Important interfaces for the new architecture:
- ` ISmartProxyOptions`: Main configuration with ` routes` array
- ` IRouteConfig`: Individual route configuration
- ` IRouteMatch`: Match criteria for routes
- ` IRouteTarget`: Target configuration for forwarding
2025-05-07 23:04:54 +00:00
- ` IAcmeOptions`: ACME certificate configuration
2025-05-16 15:50:46 +00:00
- ` TTlsMode`: TLS handling modes ('passthrough' | 'terminate' | 'terminate-and-reencrypt')
### New Route Configuration
` ``typescript
interface IRouteConfig {
name: string;
match: {
ports: number | number[];
domains?: string | string[];
path?: string;
headers?: Record<string, string | RegExp>;
};
action: {
type: 'forward' | 'redirect' | 'block' | 'static';
target?: {
host: string | string[] | ((context) => string);
port: number | 'preserve' | ((context) => number);
};
};
tls?: {
mode: TTlsMode;
certificate?: 'auto' | { key: string; cert: string; };
};
security?: {
authentication?: IRouteAuthentication;
rateLimit?: IRouteRateLimit;
ipAllowList?: string[];
ipBlockList?: string[];
};
}
` ``
2025-05-07 23:04:54 +00:00
### Required Properties
2025-05-16 15:50:46 +00:00
- For ` ISmartProxyOptions`, ` routes` array is the main configuration
2025-05-07 23:04:54 +00:00
- For ` IAcmeOptions`, use ` accountEmail` for the contact email
2025-05-16 15:50:46 +00:00
- Routes must have ` name`, ` match`, and ` action` properties
2025-05-07 23:04:54 +00:00
## Testing
### Test Structure
- Follow the project's test structure, using ` @push .rocks/tapbundle`
- Use ` expect(value).toEqual(expected)` for equality checks
- Use ` expect(value).toBeTruthy()` for boolean assertions
` ``typescript
tap.test('test description', async () => {
const result = someFunction();
expect(result.property).toEqual('expected value');
expect(result.valid).toBeTruthy();
});
` ``
### Cleanup
- Include a cleanup test to ensure proper test resource handling
- Add a ` stop` test to forcefully end the test when needed:
` ``typescript
tap.test('stop', async () => {
await tap.stopForcefully();
});
` ``
## Architecture Principles
### Simplicity
- Prefer direct usage of libraries instead of creating wrappers
- Don't reinvent functionality that already exists in dependencies
- Keep interfaces clean and focused, avoiding unnecessary abstraction layers
### Component Integration
- Leverage built-in integrations between components (like SmartProxy's ACME handling)
- Use parallel operations for performance (like in the ` stop()` method)
2025-05-16 15:50:46 +00:00
- Separate concerns clearly (HTTP handling vs. SMTP handling)
## Email Integration with SmartProxy
2026-02-11 16:32:49 +00:00
### Architecture (Post-Migration)
2025-05-16 15:50:46 +00:00
- Email traffic is routed through SmartProxy using automatic route generation
2026-02-11 16:32:49 +00:00
- smartmta's UnifiedEmailServer runs on internal ports and receives forwarded traffic from SmartProxy
2025-05-16 15:50:46 +00:00
- SmartProxy handles external ports (25, 587, 465) and forwards to internal ports
2026-02-11 16:32:49 +00:00
- smartmta's Rust SMTP bridge handles SMTP protocol processing
2025-05-16 15:50:46 +00:00
### Port Mapping
- External port 25 → Internal port 10025 (SMTP)
- External port 587 → Internal port 10587 (Submission)
- External port 465 → Internal port 10465 (SMTPS)
### TLS Handling
2026-02-11 16:32:49 +00:00
- Ports 25 and 587: Use 'passthrough' mode (STARTTLS handled by smartmta)
2025-05-16 15:50:46 +00:00
- Port 465: Use 'terminate' mode (SmartProxy handles TLS termination)
2025-06-12 11:22:18 +00:00
## SmartMetrics Integration (2025-06-12) - COMPLETED
### Overview
Fixed the UI metrics display to show accurate CPU and memory data from SmartMetrics.
### Key Findings
1. **CPU Metrics:**
- SmartMetrics provides ` cpuUsageText` as a string percentage
- MetricsManager parses it as ` cpuUsage.user` (system is always 0)
- UI was incorrectly dividing by 2, showing half the actual CPU usage
2. **Memory Metrics:**
- SmartMetrics calculates ` maxMemoryMB` as minimum of:
- V8 heap size limit
- System total memory
- Docker memory limit (if available)
- Provides ` memoryUsageBytes` (total process memory including children)
- Provides ` memoryPercentage` (pre-calculated percentage)
- UI was only showing heap usage, missing actual memory constraints
### Changes Made
1. **MetricsManager Enhanced:**
- Added ` maxMemoryMB` from SmartMetrics instance
- Added ` actualUsageBytes` from SmartMetrics data
- Added ` actualUsagePercentage` from SmartMetrics data
- Kept existing memory fields for compatibility
2. **Interface Updated:**
- Added optional fields to ` IServerStats.memoryUsage`
- Fields are optional to maintain backward compatibility
3. **UI Fixed:**
- Removed incorrect CPU division by 2
- Uses ` actualUsagePercentage` when available (falls back to heap percentage)
- Shows actual memory usage vs max memory limit (not just heap)
### Result
- CPU now shows accurate usage percentage
- Memory shows percentage of actual constraints (Docker/system/V8 limits)
2025-06-20 10:56:53 +00:00
- Better monitoring for containerized environments
## Network UI Implementation (2025-06-20) - COMPLETED
### Overview
Revamped the Network UI to display real network data from SmartProxy instead of mock data.
### Architecture
1. **MetricsManager Integration:**
- Already integrates with SmartProxy via ` dcRouter.smartProxy.getStats()`
- Extended with ` getNetworkStats()` method to expose unused metrics:
- ` getConnectionsByIP()` - Connection counts by IP address
- ` getThroughputRate()` - Real-time bandwidth rates (bytes/second)
- ` getTopIPs()` - Top connecting IPs sorted by connection count
- Note: SmartProxy base interface doesn't include all methods, manual implementation required
2. **Existing Infrastructure Leveraged:**
- ` getActiveConnections` endpoint already exists in security.handler.ts
- Enhanced to include real SmartProxy data via MetricsManager
- IConnectionInfo interface already supports network data structures
3. **State Management:**
- Added ` INetworkState` interface following existing patterns
- Created ` networkStatePart` with connections, throughput, and IP data
- Integrated with existing auto-refresh mechanism
4. **UI Changes (Minimal):**
- Removed ` generateMockData()` method and all mock generation
- Connected to real ` networkStatePart` state
- Added ` renderTopIPs()` section to display top connected IPs
- Updated traffic chart to show real request data
- Kept all existing UI components (DeesTable, DeesChartArea)
### Implementation Details
1. **Data Transformation:**
- Converts IConnectionInfo[] to INetworkRequest[] for table display
- Calculates traffic buckets based on selected time range
- Maps connection data to chart-compatible format
2. **Real Metrics Displayed:**
- Active connections count (from server stats)
- Requests per second (calculated from recent connections)
- Throughput rates (currently showing 0 until SmartProxy exposes rates)
- Top IPs with connection counts and percentages
3. **TypeScript Fixes:**
- SmartProxy methods like ` getThroughputRate()` not in base interface
- Implemented manual fallbacks for missing methods
- Fixed ` publicIpv4` → ` publicIp` property name
### Result
- Network view now shows real connection activity
- Auto-refreshes with other stats every second
- Displays actual IPs and connection counts
- No more mock/demo data
- Minimal code changes (streamlined approach)
### Throughput Data Fix (2025-06-20)
The throughput was showing 0 because:
1. MetricsManager was hardcoding throughputRate to 0, assuming the method didn't exist
2. SmartProxy's ` getStats()` returns ` IProxyStats` interface, but the actual object (` MetricsCollector`) implements ` IProxyStatsExtended`
3. ` getThroughputRate()` only exists in the extended interface
**Solution implemented:**
1. Updated MetricsManager to check if methods exist at runtime and call them
2. Added property name mapping (` bytesInPerSec` → ` bytesInPerSecond`)
3. Created new ` getNetworkStats` endpoint in security.handler.ts
4. Updated frontend to call the new endpoint for complete network metrics
2026-02-02 00:36:19 +00:00
The throughput data now flows correctly from SmartProxy → MetricsManager → API → UI.
## Email Operations Dashboard (2026-02-01)
### Overview
Replaced mock data in the email UI with real backend data from the delivery queue and security logger.
### New Files Created
- ` ts_interfaces/requests/email-ops.ts` - TypedRequest interfaces for email operations
- ` ts/opsserver/handlers/email-ops.handler.ts` - Backend handler for email operations
### Key Interfaces
- ` IReq_GetQueuedEmails` - Fetch emails from delivery queue by status
- ` IReq_GetSentEmails` - Fetch delivered emails
- ` IReq_GetFailedEmails` - Fetch failed emails
- ` IReq_ResendEmail` - Re-queue a failed email for retry
- ` IReq_GetSecurityIncidents` - Fetch security events from SecurityLogger
- ` IReq_GetBounceRecords` - Fetch bounce records and suppression list
- ` IReq_RemoveFromSuppressionList` - Remove email from suppression list
### UI Changes (ops-view-emails.ts)
- Replaced mock folders (inbox/sent/draft/trash) with operations views:
- **Queued**: Emails pending delivery
- **Sent**: Successfully delivered emails
- **Failed**: Failed emails with resend capability
- **Security**: Security incidents from SecurityLogger
- Removed ` generateMockEmails()` method
- Added state management via ` emailOpsStatePart` in appstate.ts
- Added resend button for failed emails
- Added security incident detail view
### Data Flow
` ``
UnifiedDeliveryQueue → EmailOpsHandler → TypedRequest → Frontend State → UI
SecurityLogger → EmailOpsHandler → TypedRequest → Frontend State → UI
BounceManager → EmailOpsHandler → TypedRequest → Frontend State → UI
` ``
### Backend Data Access
The handler accesses data from:
- ` dcRouter.emailServer.deliveryQueue` - Email queue items (IQueueItem)
- ` SecurityLogger.getInstance()` - Security events (ISecurityEvent)
- ` emailServer.bounceManager` - Bounce records and suppression list
## OpsServer UI Fixes (2026-02-02)
### Configuration Page Fix
The configuration page had field name mismatches between frontend and backend:
- Frontend expected ` server` and ` storage` sections
- Backend returns ` proxy` section (not ` server`)
- Backend has no ` storage` section
**Fix**: Updated ` ops-view-config.ts` to use correct section names:
- ` proxy` instead of ` server`
- Removed non-existent ` storage` section
- Added optional chaining (` ?.`) for safety
### Auth Persistence Fix
Login state was using ` 'soft'` mode in Smartstate which is memory-only:
- User login was lost on page refresh
- State reset to logged out after browser restart
**Changes**:
1. ` ts_web/appstate.ts`: Changed loginStatePart from ` 'soft'` to ` 'persistent'`
- Now uses IndexedDB to persist across browser sessions
2. ` ts/opsserver/handlers/admin.handler.ts`: JWT expiry changed from 7 days to 24 hours
3. ` ts_web/elements/ops-dashboard.ts`: Added JWT expiry check on session restore
- Validates stored JWT hasn't expired before auto-logging in
2026-02-03 23:26:51 +00:00
- Clears expired sessions and shows login form
## Config UI Read-Only Conversion (2026-02-03)
### Overview
The configuration UI has been converted from an editable interface to a read-only display. DcRouter is configured through code or remotely, not through the UI.
### Changes Made
1. **Backend (` ts/opsserver/handlers/config.handler.ts`)**:
- Removed ` updateConfiguration` handler
- Removed ` updateConfiguration()` private method
- Kept ` getConfiguration` handler (read-only)
2. **Interfaces (` ts_interfaces/requests/config.ts`)**:
- Removed ` IReq_UpdateConfiguration` interface
- Kept ` IReq_GetConfiguration` interface
3. **Frontend (` ts_web/elements/ops-view-config.ts`)**:
- Removed ` editingSection` and ` editedConfig` state properties
- Removed ` startEdit()`, ` cancelEdit()`, ` saveConfig()` methods
- Removed Edit/Save/Cancel buttons
- Removed warning banner about immediate changes
- Enhanced read-only display with:
- Status badges for boolean values (enabled/disabled)
- Array display as pills/tags with counts
- Section icons (mail, globe, network, shield)
- Better formatting for numbers and byte sizes
- Empty state handling ("Not configured", "None configured")
- Info note explaining configuration is read-only
4. **State Management (` ts_web/appstate.ts`)**:
- Removed ` updateConfigurationAction`
- Kept ` fetchConfigurationAction` (read-only)
5. **Tests (` test/test.protected-endpoint.ts`)**:
- Replaced ` updateConfiguration` tests with ` verifyIdentity` tests
- Added test for read-only config access
- Kept auth flow testing with different protected endpoint
6. **Documentation**:
- ` readme.md`: Updated API endpoints to show config as read-only
- ` ts_web/readme.md`: Removed ` updateConfigurationAction` from actions list
- ` ts_interfaces/readme.md`: Removed ` IReq_UpdateConfiguration` from table
### Visual Display Features
- Boolean values shown as colored badges (green=enabled, red=disabled)
- Arrays displayed as pills with count summaries
- Section headers with relevant Lucide icons
- Numbers formatted with locale separators
- Byte sizes auto-formatted (B, KB, MB, GB)
- Time values shown with "seconds" suffix
2026-02-10 11:22:15 +00:00
- Nested objects with visual indentation
## Smartdata Cache System (2026-02-03)
### Overview
2026-02-12 14:20:42 +00:00
DcRouter now uses smartdata + LocalTsmDb for persistent caching. Data is stored at ` ~/.serve.zone/dcrouter/tsmdb`.
2026-02-10 11:22:15 +00:00
### Technology Stack
| Layer | Package | Purpose |
|-------|---------|---------|
| ORM | ` @push .rocks/smartdata` | Document classes, decorators, queries |
| Database | ` @push .rocks/smartmongo` (LocalTsmDb) | Embedded TsmDB via Unix socket |
### TC39 Decorators
The project uses TC39 Stage 3 decorators (not experimental decorators). The tsconfig was updated:
- Removed ` experimentalDecorators: true`
- Removed ` emitDecoratorMetadata: true`
This is required for smartdata v7+ compatibility.
### Cache Document Classes
Located in ` ts/cache/documents/`:
| Class | Purpose | Default TTL |
|-------|---------|-------------|
| ` CachedEmail` | Email queue items | 30 days |
| ` CachedIPReputation` | IP reputation lookups | 24 hours |
2026-02-11 16:32:49 +00:00
Note: CachedBounce, CachedSuppression, and CachedDKIMKey were removed in the smartmta migration (smartmta handles its own persistence for those).
2026-02-10 11:22:15 +00:00
### Usage Pattern
` ``typescript
// Document classes use smartdata decorators
@plugins.smartdata.Collection(() => getDb())
export class CachedEmail extends CachedDocument<CachedEmail> {
@plugins.smartdata.svDb()
public createdAt: Date = new Date();
@plugins.smartdata.svDb()
public expiresAt: Date = new Date(Date.now() + TTL.DAYS_30);
@plugins.smartdata.unI()
@plugins.smartdata.svDb()
public id: string;
// ...
}
// Query examples
const email = await CachedEmail.getInstance({ id: 'abc123' });
const pending = await CachedEmail.getInstances({ status: 'pending' });
await email.save();
await email.delete();
` ``
### Configuration
` ``typescript
const dcRouter = new DcRouter({
cacheConfig: {
enabled: true,
2026-02-12 14:20:42 +00:00
storagePath: '~/.serve.zone/dcrouter/tsmdb',
2026-02-10 11:22:15 +00:00
dbName: 'dcrouter',
cleanupIntervalHours: 1,
ttlConfig: {
emails: 30, // days
ipReputation: 1, // days
bounces: 30, // days
dkimKeys: 90, // days
suppression: 30 // days
}
}
});
` ``
### Cache Cleaner
- Runs hourly by default (configurable via ` cleanupIntervalHours`)
- Finds and deletes documents where ` expiresAt < now()`
- Uses smartdata's ` getInstances()` + ` delete()` pattern
### Key Files
- ` ts/cache/classes.cachedb.ts` - CacheDb singleton wrapper
- ` ts/cache/classes.cached.document.ts` - Base class with TTL support
- ` ts/cache/classes.cache.cleaner.ts` - Periodic cleanup service
- ` ts/cache/documents/*.ts` - Document class definitions