@serve.zone/dcrouter 12.2.6 → 12.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_serve/bundle.js +1453 -1134
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +6 -0
- package/dist_ts_web/appstate.js +22 -1
- package/dist_ts_web/elements/ops-dashboard.d.ts +2 -0
- package/dist_ts_web/elements/ops-dashboard.js +35 -2
- package/dist_ts_web/elements/ops-view-network.js +5 -15
- package/dist_ts_web/elements/ops-view-overview.js +5 -1
- package/dist_ts_web/elements/ops-view-routes.d.ts +3 -0
- package/dist_ts_web/elements/ops-view-routes.js +142 -1
- package/package.json +3 -3
- package/readme.md +77 -63
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +31 -0
- package/ts_web/elements/ops-dashboard.ts +29 -1
- package/ts_web/elements/ops-view-network.ts +4 -14
- package/ts_web/elements/ops-view-overview.ts +4 -0
- package/ts_web/elements/ops-view-routes.ts +159 -0
- package/ts_web/readme.md +6 -0
package/readme.md
CHANGED
|
@@ -93,10 +93,11 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
93
93
|
- **Socket-handler mode** — direct socket passing eliminates internal port hops
|
|
94
94
|
- **Real-time metrics** via SmartMetrics (CPU, memory, connections, throughput)
|
|
95
95
|
|
|
96
|
-
### 💾
|
|
97
|
-
- **
|
|
98
|
-
- **
|
|
96
|
+
### 💾 Unified Database
|
|
97
|
+
- **Two deployment modes**: embedded LocalSmartDb (zero-config) or external MongoDB
|
|
98
|
+
- **15 document classes** covering routes, certs, VPN, RADIUS, security profiles, network targets, and caches
|
|
99
99
|
- **Automatic TTL-based cleanup** for cached emails and IP reputation data
|
|
100
|
+
- **Reusable references** — security profiles and network targets that propagate changes to all referencing routes
|
|
100
101
|
|
|
101
102
|
### 🖥️ OpsServer Dashboard
|
|
102
103
|
- **Web-based management interface** with real-time monitoring
|
|
@@ -104,7 +105,9 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
104
105
|
- **Live views** for connections, email queues, DNS queries, RADIUS sessions, certificates, remote ingress edges, VPN clients, and security events
|
|
105
106
|
- **Domain-centric certificate overview** with backoff status and one-click reprovisioning
|
|
106
107
|
- **Remote ingress management** with connection token generation and one-click copy
|
|
107
|
-
- **
|
|
108
|
+
- **Security profiles & network targets** — reusable security configurations and host:port targets with propagation to referencing routes
|
|
109
|
+
- **Global warning banners** when database is disabled (management features unavailable)
|
|
110
|
+
- **Read-only configuration display** for system overview
|
|
108
111
|
- **Smart tab visibility handling** — auto-pauses all polling, WebSocket connections, and chart updates when the browser tab is hidden, preventing resource waste and tab freezing
|
|
109
112
|
|
|
110
113
|
### 🔧 Programmatic API Client
|
|
@@ -269,11 +272,8 @@ const router = new DcRouter({
|
|
|
269
272
|
],
|
|
270
273
|
},
|
|
271
274
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// Cache database
|
|
276
|
-
cacheConfig: { enabled: true, storagePath: '~/.serve.zone/dcrouter/tsmdb' },
|
|
275
|
+
// Unified database (embedded LocalSmartDb or external MongoDB)
|
|
276
|
+
dbConfig: { enabled: true },
|
|
277
277
|
|
|
278
278
|
// TLS & ACME
|
|
279
279
|
tls: { contactEmail: 'admin@example.com' },
|
|
@@ -311,8 +311,7 @@ graph TB
|
|
|
311
311
|
CM[Certificate Manager<br/><i>smartacme v9</i>]
|
|
312
312
|
OS[OpsServer Dashboard]
|
|
313
313
|
MM[Metrics Manager]
|
|
314
|
-
|
|
315
|
-
CD[Cache Database]
|
|
314
|
+
DB2[DcRouterDb<br/><i>smartdata + smartdb</i>]
|
|
316
315
|
end
|
|
317
316
|
|
|
318
317
|
subgraph "Backend Services"
|
|
@@ -339,8 +338,7 @@ graph TB
|
|
|
339
338
|
DC --> CM
|
|
340
339
|
DC --> OS
|
|
341
340
|
DC --> MM
|
|
342
|
-
DC -->
|
|
343
|
-
DC --> CD
|
|
341
|
+
DC --> DB2
|
|
344
342
|
|
|
345
343
|
SP --> WEB
|
|
346
344
|
SP --> API
|
|
@@ -365,8 +363,7 @@ graph TB
|
|
|
365
363
|
| **RemoteIngress** | `@serve.zone/remoteingress` | Distributed edge tunneling with Rust data plane and TS management |
|
|
366
364
|
| **OpsServer** | `@api.global/typedserver` | Web dashboard + TypedRequest API for monitoring and management |
|
|
367
365
|
| **MetricsManager** | `@push.rocks/smartmetrics` | Real-time metrics collection (CPU, memory, email, DNS, security) |
|
|
368
|
-
| **
|
|
369
|
-
| **CacheDb** | `@push.rocks/smartdb` | Embedded MongoDB-compatible database (LocalSmartDb) for persistent caching |
|
|
366
|
+
| **DcRouterDb** | `@push.rocks/smartdata` + `@push.rocks/smartdb` | Unified database — embedded LocalSmartDb or external MongoDB for all persistence |
|
|
370
367
|
|
|
371
368
|
### How It Works
|
|
372
369
|
|
|
@@ -509,24 +506,16 @@ interface IDcRouterOptions {
|
|
|
509
506
|
};
|
|
510
507
|
dnsChallenge?: { cloudflareApiKey?: string };
|
|
511
508
|
|
|
512
|
-
// ──
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
readFunction?: (key: string) => Promise<string>;
|
|
516
|
-
writeFunction?: (key: string, value: string) => Promise<void>;
|
|
517
|
-
};
|
|
518
|
-
cacheConfig?: {
|
|
509
|
+
// ── Database ────────────────────────────────────────────────────
|
|
510
|
+
/** Unified database for all persistence (routes, certs, VPN, RADIUS, etc.) */
|
|
511
|
+
dbConfig?: {
|
|
519
512
|
enabled?: boolean; // default: true
|
|
513
|
+
mongoDbUrl?: string; // External MongoDB URL (omit for embedded LocalSmartDb)
|
|
520
514
|
storagePath?: string; // default: '~/.serve.zone/dcrouter/tsmdb'
|
|
521
515
|
dbName?: string; // default: 'dcrouter'
|
|
522
516
|
cleanupIntervalHours?: number; // default: 1
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
ipReputation?: number; // default: 1 day
|
|
526
|
-
bounces?: number; // default: 30 days
|
|
527
|
-
dkimKeys?: number; // default: 90 days
|
|
528
|
-
suppression?: number; // default: 30 days
|
|
529
|
-
};
|
|
517
|
+
seedOnEmpty?: boolean; // Seed default profiles/targets if DB is empty
|
|
518
|
+
seedData?: object; // Custom seed data
|
|
530
519
|
};
|
|
531
520
|
}
|
|
532
521
|
```
|
|
@@ -1213,49 +1202,55 @@ The OpsServer includes a **Certificates** view showing:
|
|
|
1213
1202
|
- One-click reprovisioning per domain
|
|
1214
1203
|
- Certificate import and export
|
|
1215
1204
|
|
|
1216
|
-
## Storage &
|
|
1205
|
+
## Storage & Database
|
|
1217
1206
|
|
|
1218
|
-
|
|
1207
|
+
DcRouter uses a **unified database** (`DcRouterDb`) powered by [`@push.rocks/smartdata`](https://code.foss.global/push.rocks/smartdata) + [`@push.rocks/smartdb`](https://code.foss.global/push.rocks/smartdb) for all persistence. It supports two modes:
|
|
1219
1208
|
|
|
1220
|
-
|
|
1209
|
+
### Embedded LocalSmartDb (Default)
|
|
1221
1210
|
|
|
1222
|
-
|
|
1223
|
-
// Filesystem backend
|
|
1224
|
-
storage: { fsPath: '/var/lib/dcrouter/data' }
|
|
1211
|
+
Zero-config, file-based MongoDB-compatible database — no external services needed:
|
|
1225
1212
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
writeFunction: async (key, value) => await redis.set(key, value)
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
// In-memory (development only — data lost on restart)
|
|
1233
|
-
// Simply omit the storage config
|
|
1213
|
+
```typescript
|
|
1214
|
+
dbConfig: { enabled: true }
|
|
1215
|
+
// Data stored at ~/.serve.zone/dcrouter/tsmdb by default
|
|
1234
1216
|
```
|
|
1235
1217
|
|
|
1236
|
-
|
|
1218
|
+
### External MongoDB
|
|
1237
1219
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
An embedded MongoDB-compatible database (via smartdata + smartdb) for persistent caching with automatic TTL cleanup:
|
|
1220
|
+
Connect to an existing MongoDB instance:
|
|
1241
1221
|
|
|
1242
1222
|
```typescript
|
|
1243
|
-
|
|
1223
|
+
dbConfig: {
|
|
1244
1224
|
enabled: true,
|
|
1245
|
-
|
|
1225
|
+
mongoDbUrl: 'mongodb://localhost:27017',
|
|
1246
1226
|
dbName: 'dcrouter',
|
|
1247
|
-
cleanupIntervalHours: 1,
|
|
1248
|
-
ttlConfig: {
|
|
1249
|
-
emails: 30, // days
|
|
1250
|
-
ipReputation: 1, // days
|
|
1251
|
-
bounces: 30, // days
|
|
1252
|
-
dkimKeys: 90, // days
|
|
1253
|
-
suppression: 30 // days
|
|
1254
|
-
}
|
|
1255
1227
|
}
|
|
1256
1228
|
```
|
|
1257
1229
|
|
|
1258
|
-
|
|
1230
|
+
### Disabling the Database
|
|
1231
|
+
|
|
1232
|
+
For static, constructor-only deployments where no runtime management is needed:
|
|
1233
|
+
|
|
1234
|
+
```typescript
|
|
1235
|
+
dbConfig: { enabled: false }
|
|
1236
|
+
// Routes come exclusively from constructor config — no CRUD, no persistence
|
|
1237
|
+
// OpsServer still runs but management features are disabled
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
### What's Stored
|
|
1241
|
+
|
|
1242
|
+
DcRouterDb persists all runtime state across 15 document classes:
|
|
1243
|
+
|
|
1244
|
+
| Category | Documents | Purpose |
|
|
1245
|
+
|----------|-----------|---------|
|
|
1246
|
+
| **Routes** | `StoredRouteDoc`, `RouteOverrideDoc` | Programmatic routes and hardcoded route overrides |
|
|
1247
|
+
| **Certificates** | `ProxyCertDoc`, `AcmeCertDoc`, `CertBackoffDoc` | TLS certs, ACME state, per-domain backoff |
|
|
1248
|
+
| **Auth** | `ApiTokenDoc` | API token storage |
|
|
1249
|
+
| **Remote Ingress** | `RemoteIngressEdgeDoc` | Edge node registrations |
|
|
1250
|
+
| **VPN** | `VpnServerKeysDoc`, `VpnClientDoc` | Server keys and client registrations |
|
|
1251
|
+
| **RADIUS** | `VlanMappingsDoc`, `AccountingSessionDoc` | VLAN mappings and accounting sessions |
|
|
1252
|
+
| **References** | `SecurityProfileDoc`, `NetworkTargetDoc` | Reusable security profiles and network targets |
|
|
1253
|
+
| **Cache** | `CachedEmailDoc`, `CachedIpReputationDoc` | TTL-based caches with automatic cleanup |
|
|
1259
1254
|
|
|
1260
1255
|
## Security Features
|
|
1261
1256
|
|
|
@@ -1324,6 +1319,8 @@ The OpsServer provides a web-based management interface served on port 3000 by d
|
|
|
1324
1319
|
| 🔐 **Certificates** | Domain-centric certificate overview, status, backoff info, reprovisioning, import/export |
|
|
1325
1320
|
| 🌍 **RemoteIngress** | Edge node management, connection status, token generation, enable/disable |
|
|
1326
1321
|
| 🔐 **VPN** | VPN client management, server status, create/toggle/export/rotate/delete clients |
|
|
1322
|
+
| 🛡️ **Security Profiles** | Reusable security configurations (IP allow/block lists, rate limits) |
|
|
1323
|
+
| 🎯 **Network Targets** | Reusable host:port destinations for route references |
|
|
1327
1324
|
| 📡 **RADIUS** | NAS client management, VLAN mappings, session monitoring, accounting |
|
|
1328
1325
|
| 📜 **Logs** | Real-time log viewer with level filtering and search |
|
|
1329
1326
|
| ⚙️ **Configuration** | Read-only view of current system configuration |
|
|
@@ -1410,6 +1407,22 @@ All management is done via TypedRequest over HTTP POST to `/typedrequest`:
|
|
|
1410
1407
|
'setVlanMapping' // Add/update VLAN mapping
|
|
1411
1408
|
'removeVlanMapping' // Remove VLAN mapping
|
|
1412
1409
|
'testVlanAssignment' // Test what VLAN a MAC gets
|
|
1410
|
+
|
|
1411
|
+
// Security Profiles
|
|
1412
|
+
'getSecurityProfiles' // List all security profiles
|
|
1413
|
+
'getSecurityProfile' // Get a single profile by ID
|
|
1414
|
+
'createSecurityProfile' // Create a reusable security profile
|
|
1415
|
+
'updateSecurityProfile' // Update a profile (propagates to referencing routes)
|
|
1416
|
+
'deleteSecurityProfile' // Delete a profile (with optional force)
|
|
1417
|
+
'getSecurityProfileUsage' // Get routes referencing a profile
|
|
1418
|
+
|
|
1419
|
+
// Network Targets
|
|
1420
|
+
'getNetworkTargets' // List all network targets
|
|
1421
|
+
'getNetworkTarget' // Get a single target by ID
|
|
1422
|
+
'createNetworkTarget' // Create a reusable host:port target
|
|
1423
|
+
'updateNetworkTarget' // Update a target (propagates to referencing routes)
|
|
1424
|
+
'deleteNetworkTarget' // Delete a target (with optional force)
|
|
1425
|
+
'getNetworkTargetUsage' // Get routes referencing a target
|
|
1413
1426
|
```
|
|
1414
1427
|
|
|
1415
1428
|
## API Client
|
|
@@ -1518,12 +1531,12 @@ const router = new DcRouter(options: IDcRouterOptions);
|
|
|
1518
1531
|
| `remoteIngressManager` | `RemoteIngressManager` | Edge registration CRUD manager |
|
|
1519
1532
|
| `tunnelManager` | `TunnelManager` | Tunnel lifecycle and status manager |
|
|
1520
1533
|
| `vpnManager` | `VpnManager` | VPN server lifecycle and client CRUD manager |
|
|
1521
|
-
| `storageManager` | `StorageManager` | Storage backend |
|
|
1522
1534
|
| `opsServer` | `OpsServer` | OpsServer/dashboard instance |
|
|
1523
1535
|
| `metricsManager` | `MetricsManager` | Metrics collector |
|
|
1524
|
-
| `
|
|
1525
|
-
| `
|
|
1526
|
-
| `
|
|
1536
|
+
| `dcRouterDb` | `DcRouterDb` | Unified database instance (smartdata + smartdb) |
|
|
1537
|
+
| `routeConfigManager` | `RouteConfigManager` | Programmatic route CRUD manager |
|
|
1538
|
+
| `apiTokenManager` | `ApiTokenManager` | API token management |
|
|
1539
|
+
| `referenceResolver` | `ReferenceResolver` | Security profile and network target resolver |
|
|
1527
1540
|
|
|
1528
1541
|
### Re-exported Types
|
|
1529
1542
|
|
|
@@ -1589,7 +1602,8 @@ tstest test/test.opsserver-api.ts --verbose --timeout 60
|
|
|
1589
1602
|
| `test.jwt-auth.ts` | JWT login, verification, logout, invalid credentials | 8 |
|
|
1590
1603
|
| `test.opsserver-api.ts` | Health, statistics, configuration, log APIs | 8 |
|
|
1591
1604
|
| `test.protected-endpoint.ts` | Admin auth, identity verification, public endpoints | 8 |
|
|
1592
|
-
| `test.
|
|
1605
|
+
| `test.reference-resolver.ts` | Security profiles, network targets, route resolution | 20 |
|
|
1606
|
+
| `test.security-profiles-api.ts` | Profile/target API endpoints, auth enforcement | 13 |
|
|
1593
1607
|
|
|
1594
1608
|
## Docker / OCI Container Deployment
|
|
1595
1609
|
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts_web/appstate.ts
CHANGED
|
@@ -1441,6 +1441,37 @@ export const createRouteAction = routeManagementStatePart.createAction<{
|
|
|
1441
1441
|
}
|
|
1442
1442
|
});
|
|
1443
1443
|
|
|
1444
|
+
export const updateRouteAction = routeManagementStatePart.createAction<{
|
|
1445
|
+
id: string;
|
|
1446
|
+
route?: any;
|
|
1447
|
+
enabled?: boolean;
|
|
1448
|
+
metadata?: any;
|
|
1449
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
|
|
1450
|
+
const context = getActionContext();
|
|
1451
|
+
const currentState = statePartArg.getState()!;
|
|
1452
|
+
|
|
1453
|
+
try {
|
|
1454
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1455
|
+
interfaces.requests.IReq_UpdateRoute
|
|
1456
|
+
>('/typedrequest', 'updateRoute');
|
|
1457
|
+
|
|
1458
|
+
await request.fire({
|
|
1459
|
+
identity: context.identity!,
|
|
1460
|
+
id: dataArg.id,
|
|
1461
|
+
route: dataArg.route,
|
|
1462
|
+
enabled: dataArg.enabled,
|
|
1463
|
+
metadata: dataArg.metadata,
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
return await actionContext!.dispatch(fetchMergedRoutesAction, null);
|
|
1467
|
+
} catch (error: unknown) {
|
|
1468
|
+
return {
|
|
1469
|
+
...currentState,
|
|
1470
|
+
error: error instanceof Error ? error.message : 'Failed to update route',
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1444
1475
|
export const deleteRouteAction = routeManagementStatePart.createAction<string>(
|
|
1445
1476
|
async (statePartArg, routeId, actionContext): Promise<IRouteManagementState> => {
|
|
1446
1477
|
const context = getActionContext();
|
|
@@ -2,7 +2,6 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import * as appstate from '../appstate.js';
|
|
3
3
|
import * as interfaces from '../../dist_ts_interfaces/index.js';
|
|
4
4
|
import { appRouter } from '../router.js';
|
|
5
|
-
|
|
6
5
|
import {
|
|
7
6
|
DeesElement,
|
|
8
7
|
css,
|
|
@@ -43,6 +42,12 @@ export class OpsDashboard extends DeesElement {
|
|
|
43
42
|
theme: 'light',
|
|
44
43
|
};
|
|
45
44
|
|
|
45
|
+
@state() accessor configState: appstate.IConfigState = {
|
|
46
|
+
config: null,
|
|
47
|
+
isLoading: false,
|
|
48
|
+
error: null,
|
|
49
|
+
};
|
|
50
|
+
|
|
46
51
|
// Store viewTabs as a property to maintain object references
|
|
47
52
|
private viewTabs = [
|
|
48
53
|
{
|
|
@@ -112,6 +117,20 @@ export class OpsDashboard extends DeesElement {
|
|
|
112
117
|
},
|
|
113
118
|
];
|
|
114
119
|
|
|
120
|
+
private get globalMessages() {
|
|
121
|
+
const messages: Array<{ id: string; type: string; message: string; dismissible?: boolean }> = [];
|
|
122
|
+
const config = this.configState.config;
|
|
123
|
+
if (config && !config.cache.enabled) {
|
|
124
|
+
messages.push({
|
|
125
|
+
id: 'db-disabled',
|
|
126
|
+
type: 'warning',
|
|
127
|
+
message: 'Database is disabled. Creating and editing routes, profiles, targets, and API tokens is not available.',
|
|
128
|
+
dismissible: false,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return messages;
|
|
132
|
+
}
|
|
133
|
+
|
|
115
134
|
/**
|
|
116
135
|
* Get the current view tab based on the UI state's activeView.
|
|
117
136
|
* Used to pass the correct selectedView to dees-simple-appdash on initial render.
|
|
@@ -137,6 +156,14 @@ export class OpsDashboard extends DeesElement {
|
|
|
137
156
|
});
|
|
138
157
|
this.rxSubscriptions.push(loginSubscription);
|
|
139
158
|
|
|
159
|
+
// Subscribe to config state (for global warnings)
|
|
160
|
+
const configSubscription = appstate.configStatePart
|
|
161
|
+
.select((stateArg) => stateArg)
|
|
162
|
+
.subscribe((configState) => {
|
|
163
|
+
this.configState = configState;
|
|
164
|
+
});
|
|
165
|
+
this.rxSubscriptions.push(configSubscription);
|
|
166
|
+
|
|
140
167
|
// Subscribe to UI state
|
|
141
168
|
const uiSubscription = appstate.uiStatePart
|
|
142
169
|
.select((stateArg) => stateArg)
|
|
@@ -205,6 +232,7 @@ export class OpsDashboard extends DeesElement {
|
|
|
205
232
|
name="DCRouter OpsServer"
|
|
206
233
|
.viewTabs=${this.viewTabs}
|
|
207
234
|
.selectedView=${this.currentViewTab}
|
|
235
|
+
.globalMessages=${this.globalMessages}
|
|
208
236
|
>
|
|
209
237
|
</dees-simple-appdash>
|
|
210
238
|
</dees-simple-login>
|
|
@@ -287,27 +287,17 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
287
287
|
{
|
|
288
288
|
name: 'Inbound',
|
|
289
289
|
data: this.trafficDataIn,
|
|
290
|
-
color: '#22c55e',
|
|
290
|
+
color: '#22c55e',
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
name: 'Outbound',
|
|
294
294
|
data: this.trafficDataOut,
|
|
295
|
-
color: '#8b5cf6',
|
|
295
|
+
color: '#8b5cf6',
|
|
296
296
|
}
|
|
297
297
|
]}
|
|
298
|
-
.
|
|
298
|
+
.realtimeMode=${true}
|
|
299
|
+
.rollingWindow=${300000}
|
|
299
300
|
.yAxisFormatter=${(val: number) => `${val} Mbit/s`}
|
|
300
|
-
.tooltipFormatter=${(point: any) => {
|
|
301
|
-
const mbps = point.y || 0;
|
|
302
|
-
const seriesName = point.series?.name || 'Throughput';
|
|
303
|
-
const timestamp = new Date(point.x).toLocaleTimeString();
|
|
304
|
-
return `
|
|
305
|
-
<div style="padding: 8px;">
|
|
306
|
-
<div style="font-weight: bold; margin-bottom: 4px;">${timestamp}</div>
|
|
307
|
-
<div>${seriesName}: ${mbps.toFixed(2)} Mbit/s</div>
|
|
308
|
-
</div>
|
|
309
|
-
`;
|
|
310
|
-
}}
|
|
311
301
|
></dees-chart-area>
|
|
312
302
|
|
|
313
303
|
<!-- Top IPs Section -->
|
|
@@ -121,11 +121,15 @@ export class OpsViewOverview extends DeesElement {
|
|
|
121
121
|
<dees-chart-area
|
|
122
122
|
.label=${'Email Traffic (24h)'}
|
|
123
123
|
.series=${this.getEmailTrafficSeries()}
|
|
124
|
+
.realtimeMode=${true}
|
|
125
|
+
.rollingWindow=${86400000}
|
|
124
126
|
.yAxisFormatter=${(val: number) => `${val}`}
|
|
125
127
|
></dees-chart-area>
|
|
126
128
|
<dees-chart-area
|
|
127
129
|
.label=${'DNS Queries (24h)'}
|
|
128
130
|
.series=${this.getDnsQuerySeries()}
|
|
131
|
+
.realtimeMode=${true}
|
|
132
|
+
.rollingWindow=${86400000}
|
|
129
133
|
.yAxisFormatter=${(val: number) => `${val}`}
|
|
130
134
|
></dees-chart-area>
|
|
131
135
|
<dees-chart-log
|
|
@@ -204,7 +204,10 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
204
204
|
? html`
|
|
205
205
|
<sz-route-list-view
|
|
206
206
|
.routes=${szRoutes}
|
|
207
|
+
.showActionsFilter=${(route: any) => route.tags?.includes('programmatic') ?? false}
|
|
207
208
|
@route-click=${(e: CustomEvent) => this.handleRouteClick(e)}
|
|
209
|
+
@route-edit=${(e: CustomEvent) => this.handleRouteEdit(e)}
|
|
210
|
+
@route-delete=${(e: CustomEvent) => this.handleRouteDelete(e)}
|
|
208
211
|
></sz-route-list-view>
|
|
209
212
|
`
|
|
210
213
|
: html`
|
|
@@ -337,6 +340,162 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
337
340
|
}
|
|
338
341
|
}
|
|
339
342
|
|
|
343
|
+
private async handleRouteEdit(e: CustomEvent) {
|
|
344
|
+
const clickedRoute = e.detail;
|
|
345
|
+
if (!clickedRoute) return;
|
|
346
|
+
|
|
347
|
+
const merged = this.routeState.mergedRoutes.find(
|
|
348
|
+
(mr) => mr.route.name === clickedRoute.name,
|
|
349
|
+
);
|
|
350
|
+
if (!merged || !merged.storedRouteId) return;
|
|
351
|
+
|
|
352
|
+
this.showEditRouteDialog(merged);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private async handleRouteDelete(e: CustomEvent) {
|
|
356
|
+
const clickedRoute = e.detail;
|
|
357
|
+
if (!clickedRoute) return;
|
|
358
|
+
|
|
359
|
+
const merged = this.routeState.mergedRoutes.find(
|
|
360
|
+
(mr) => mr.route.name === clickedRoute.name,
|
|
361
|
+
);
|
|
362
|
+
if (!merged || !merged.storedRouteId) return;
|
|
363
|
+
|
|
364
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
365
|
+
await DeesModal.createAndShow({
|
|
366
|
+
heading: `Delete Route: ${merged.route.name}`,
|
|
367
|
+
content: html`
|
|
368
|
+
<div style="color: #ccc; padding: 8px 0;">
|
|
369
|
+
<p>Are you sure you want to delete this route? This action cannot be undone.</p>
|
|
370
|
+
</div>
|
|
371
|
+
`,
|
|
372
|
+
menuOptions: [
|
|
373
|
+
{
|
|
374
|
+
name: 'Cancel',
|
|
375
|
+
iconName: 'lucide:x',
|
|
376
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: 'Delete',
|
|
380
|
+
iconName: 'lucide:trash-2',
|
|
381
|
+
action: async (modalArg: any) => {
|
|
382
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
383
|
+
appstate.deleteRouteAction,
|
|
384
|
+
merged.storedRouteId!,
|
|
385
|
+
);
|
|
386
|
+
await modalArg.destroy();
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private async showEditRouteDialog(merged: interfaces.data.IMergedRoute) {
|
|
394
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
395
|
+
const profiles = this.profilesTargetsState.profiles;
|
|
396
|
+
const targets = this.profilesTargetsState.targets;
|
|
397
|
+
|
|
398
|
+
const profileOptions = [
|
|
399
|
+
{ key: '', option: '(none — inline security)' },
|
|
400
|
+
...profiles.map((p) => ({
|
|
401
|
+
key: p.id,
|
|
402
|
+
option: `${p.name}${p.description ? ' — ' + p.description : ''}`,
|
|
403
|
+
})),
|
|
404
|
+
];
|
|
405
|
+
const targetOptions = [
|
|
406
|
+
{ key: '', option: '(none — inline target)' },
|
|
407
|
+
...targets.map((t) => ({
|
|
408
|
+
key: t.id,
|
|
409
|
+
option: `${t.name} (${Array.isArray(t.host) ? t.host.join(',') : t.host}:${t.port})`,
|
|
410
|
+
})),
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const route = merged.route;
|
|
414
|
+
const currentPorts = Array.isArray(route.match.ports)
|
|
415
|
+
? route.match.ports.map((p: any) => typeof p === 'number' ? String(p) : `${p.from}-${p.to}`).join(', ')
|
|
416
|
+
: String(route.match.ports);
|
|
417
|
+
const currentDomains = route.match.domains
|
|
418
|
+
? (Array.isArray(route.match.domains) ? route.match.domains.join(', ') : route.match.domains)
|
|
419
|
+
: '';
|
|
420
|
+
const firstTarget = route.action.targets?.[0];
|
|
421
|
+
const currentTargetHost = firstTarget
|
|
422
|
+
? (Array.isArray(firstTarget.host) ? firstTarget.host[0] : firstTarget.host)
|
|
423
|
+
: '';
|
|
424
|
+
const currentTargetPort = firstTarget?.port != null ? String(firstTarget.port) : '';
|
|
425
|
+
|
|
426
|
+
await DeesModal.createAndShow({
|
|
427
|
+
heading: `Edit Route: ${route.name}`,
|
|
428
|
+
content: html`
|
|
429
|
+
<dees-form>
|
|
430
|
+
<dees-input-text .key=${'name'} .label=${'Route Name'} .value=${route.name || ''} .required=${true}></dees-input-text>
|
|
431
|
+
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .value=${currentPorts} .required=${true}></dees-input-text>
|
|
432
|
+
<dees-input-text .key=${'domains'} .label=${'Domains (comma-separated, optional)'} .value=${currentDomains}></dees-input-text>
|
|
433
|
+
<dees-input-dropdown .key=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions} .selectedKey=${merged.metadata?.securityProfileRef || ''}></dees-input-dropdown>
|
|
434
|
+
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedKey=${merged.metadata?.networkTargetRef || ''}></dees-input-dropdown>
|
|
435
|
+
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
|
|
436
|
+
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'} .value=${currentTargetPort}></dees-input-text>
|
|
437
|
+
</dees-form>
|
|
438
|
+
`,
|
|
439
|
+
menuOptions: [
|
|
440
|
+
{
|
|
441
|
+
name: 'Cancel',
|
|
442
|
+
iconName: 'lucide:x',
|
|
443
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: 'Save',
|
|
447
|
+
iconName: 'lucide:check',
|
|
448
|
+
action: async (modalArg: any) => {
|
|
449
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
450
|
+
if (!form) return;
|
|
451
|
+
const formData = await form.collectFormData();
|
|
452
|
+
if (!formData.name || !formData.ports) return;
|
|
453
|
+
|
|
454
|
+
const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
|
|
455
|
+
const domains = formData.domains
|
|
456
|
+
? formData.domains.split(',').map((d: string) => d.trim()).filter(Boolean)
|
|
457
|
+
: undefined;
|
|
458
|
+
|
|
459
|
+
const updatedRoute: any = {
|
|
460
|
+
name: formData.name,
|
|
461
|
+
match: {
|
|
462
|
+
ports,
|
|
463
|
+
...(domains && domains.length > 0 ? { domains } : {}),
|
|
464
|
+
},
|
|
465
|
+
action: {
|
|
466
|
+
type: 'forward',
|
|
467
|
+
targets: [
|
|
468
|
+
{
|
|
469
|
+
host: formData.targetHost || 'localhost',
|
|
470
|
+
port: parseInt(formData.targetPort, 10) || 443,
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const metadata: any = {};
|
|
477
|
+
if (formData.securityProfileRef) {
|
|
478
|
+
metadata.securityProfileRef = formData.securityProfileRef;
|
|
479
|
+
}
|
|
480
|
+
if (formData.networkTargetRef) {
|
|
481
|
+
metadata.networkTargetRef = formData.networkTargetRef;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
485
|
+
appstate.updateRouteAction,
|
|
486
|
+
{
|
|
487
|
+
id: merged.storedRouteId!,
|
|
488
|
+
route: updatedRoute,
|
|
489
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
await modalArg.destroy();
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
340
499
|
private async showCreateRouteDialog() {
|
|
341
500
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
342
501
|
const profiles = this.profilesTargetsState.profiles;
|
package/ts_web/readme.md
CHANGED
|
@@ -68,6 +68,12 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
68
68
|
- API token creation, revocation, and scope management
|
|
69
69
|
- Routes tab and API Tokens tab in unified view
|
|
70
70
|
|
|
71
|
+
### 🛡️ Security Profiles & Network Targets
|
|
72
|
+
- Create, edit, and delete reusable security profiles (IP allow/block lists, rate limits, max connections)
|
|
73
|
+
- Create, edit, and delete reusable network targets (host:port destinations)
|
|
74
|
+
- In-row and context menu actions for quick editing
|
|
75
|
+
- Changes propagate automatically to all referencing routes
|
|
76
|
+
|
|
71
77
|
### ⚙️ Configuration
|
|
72
78
|
- Read-only display of current system configuration
|
|
73
79
|
- Status badges for boolean values (enabled/disabled)
|