@serve.zone/dcrouter 12.2.5 → 12.3.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/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
- ### 💾 Persistent Storage & Caching
97
- - **Multiple storage backends**: filesystem, custom functions, or in-memory
98
- - **Embedded cache database** via smartdata + smartdb (MongoDB-compatible)
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
- - **Read-only configuration display** — DcRouter is configured through code
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
- // Persistent storage
273
- storage: { fsPath: '/var/lib/dcrouter/data' },
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
- SM[Storage Manager]
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 --> SM
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
- | **StorageManager** | built-in | Pluggable key-value storage (filesystem, custom, or in-memory) |
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
- // ── Storage & Caching ─────────────────────────────────────────
513
- storage?: {
514
- fsPath?: string;
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
- ttlConfig?: {
524
- emails?: number; // default: 30 days
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 & Caching
1205
+ ## Storage & Database
1217
1206
 
1218
- ### StorageManager
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
- Provides a unified key-value interface with three backends:
1209
+ ### Embedded LocalSmartDb (Default)
1221
1210
 
1222
- ```typescript
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
- // Custom backend (Redis, S3, etc.)
1227
- storage: {
1228
- readFunction: async (key) => await redis.get(key),
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
- Used for: TLS certificates, DKIM keys, email routes, bounce/suppression lists, IP reputation data, domain configs, cert backoff state, remote ingress edge registrations.
1218
+ ### External MongoDB
1237
1219
 
1238
- ### Cache Database
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
- cacheConfig: {
1223
+ dbConfig: {
1244
1224
  enabled: true,
1245
- storagePath: '~/.serve.zone/dcrouter/tsmdb',
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
- Cached document types: `CachedEmail`, `CachedIPReputation`.
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
- | `cacheDb` | `CacheDb` | Cache database instance |
1525
- | `certProvisionScheduler` | `CertProvisionScheduler` | Per-domain backoff scheduler for cert provisioning |
1526
- | `certificateStatusMap` | `Map<string, ...>` | Domain-keyed certificate status from SmartProxy events |
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.storagemanager.ts` | Memory, filesystem, custom backends, concurrency | 8 |
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
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '12.2.5',
6
+ version: '12.3.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '12.2.5',
6
+ version: '12.3.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -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>
@@ -64,6 +64,7 @@ export class OpsViewNetworkTargets extends DeesElement {
64
64
  ];
65
65
 
66
66
  return html`
67
+ <ops-sectionheading>Network Targets</ops-sectionheading>
67
68
  <div class="targetsContainer">
68
69
  <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
69
70
  <dees-table
@@ -81,8 +82,8 @@ export class OpsViewNetworkTargets extends DeesElement {
81
82
  name: 'Create Target',
82
83
  iconName: 'lucide:plus',
83
84
  type: ['header' as const],
84
- actionFunc: async (_: any, table: any) => {
85
- await this.showCreateTargetDialog(table);
85
+ actionFunc: async () => {
86
+ await this.showCreateTargetDialog();
86
87
  },
87
88
  },
88
89
  {
@@ -96,16 +97,18 @@ export class OpsViewNetworkTargets extends DeesElement {
96
97
  {
97
98
  name: 'Edit',
98
99
  iconName: 'lucide:pencil',
99
- type: ['contextmenu' as const],
100
- actionFunc: async (target: interfaces.data.INetworkTarget, table: any) => {
101
- await this.showEditTargetDialog(target, table);
100
+ type: ['inRow', 'contextmenu'] as any,
101
+ actionFunc: async (actionData: any) => {
102
+ const target = actionData.item as interfaces.data.INetworkTarget;
103
+ await this.showEditTargetDialog(target);
102
104
  },
103
105
  },
104
106
  {
105
107
  name: 'Delete',
106
108
  iconName: 'lucide:trash2',
107
- type: ['contextmenu' as const],
108
- actionFunc: async (target: interfaces.data.INetworkTarget) => {
109
+ type: ['inRow', 'contextmenu'] as any,
110
+ actionFunc: async (actionData: any) => {
111
+ const target = actionData.item as interfaces.data.INetworkTarget;
109
112
  await this.deleteTarget(target);
110
113
  },
111
114
  },
@@ -115,7 +118,7 @@ export class OpsViewNetworkTargets extends DeesElement {
115
118
  `;
116
119
  }
117
120
 
118
- private async showCreateTargetDialog(table: any) {
121
+ private async showCreateTargetDialog() {
119
122
  const { DeesModal } = await import('@design.estate/dees-catalog');
120
123
  DeesModal.createAndShow({
121
124
  heading: 'Create Network Target',
@@ -128,10 +131,12 @@ export class OpsViewNetworkTargets extends DeesElement {
128
131
  </dees-form>
129
132
  `,
130
133
  menuOptions: [
134
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
131
135
  {
132
136
  name: 'Create',
133
137
  action: async (modalArg: any) => {
134
- const form = modalArg.shadowRoot!.querySelector('dees-form');
138
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
139
+ if (!form) return;
135
140
  const data = await form.collectFormData();
136
141
 
137
142
  await appstate.profilesTargetsStatePart.dispatchAction(appstate.createTargetAction, {
@@ -143,12 +148,11 @@ export class OpsViewNetworkTargets extends DeesElement {
143
148
  modalArg.destroy();
144
149
  },
145
150
  },
146
- { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
147
151
  ],
148
152
  });
149
153
  }
150
154
 
151
- private async showEditTargetDialog(target: interfaces.data.INetworkTarget, table: any) {
155
+ private async showEditTargetDialog(target: interfaces.data.INetworkTarget) {
152
156
  const hostStr = Array.isArray(target.host) ? target.host.join(', ') : target.host;
153
157
 
154
158
  const { DeesModal } = await import('@design.estate/dees-catalog');
@@ -163,10 +167,12 @@ export class OpsViewNetworkTargets extends DeesElement {
163
167
  </dees-form>
164
168
  `,
165
169
  menuOptions: [
170
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
166
171
  {
167
172
  name: 'Save',
168
173
  action: async (modalArg: any) => {
169
- const form = modalArg.shadowRoot!.querySelector('dees-form');
174
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
175
+ if (!form) return;
170
176
  const data = await form.collectFormData();
171
177
 
172
178
  await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateTargetAction, {
@@ -179,7 +185,6 @@ export class OpsViewNetworkTargets extends DeesElement {
179
185
  modalArg.destroy();
180
186
  },
181
187
  },
182
- { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
183
188
  ],
184
189
  });
185
190
  }
@@ -64,6 +64,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
64
64
  ];
65
65
 
66
66
  return html`
67
+ <ops-sectionheading>Security Profiles</ops-sectionheading>
67
68
  <div class="profilesContainer">
68
69
  <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
69
70
  <dees-table
@@ -89,8 +90,8 @@ export class OpsViewSecurityProfiles extends DeesElement {
89
90
  name: 'Create Profile',
90
91
  iconName: 'lucide:plus',
91
92
  type: ['header' as const],
92
- actionFunc: async (_: any, table: any) => {
93
- await this.showCreateProfileDialog(table);
93
+ actionFunc: async () => {
94
+ await this.showCreateProfileDialog();
94
95
  },
95
96
  },
96
97
  {
@@ -104,16 +105,18 @@ export class OpsViewSecurityProfiles extends DeesElement {
104
105
  {
105
106
  name: 'Edit',
106
107
  iconName: 'lucide:pencil',
107
- type: ['contextmenu' as const],
108
- actionFunc: async (profile: interfaces.data.ISecurityProfile, table: any) => {
109
- await this.showEditProfileDialog(profile, table);
108
+ type: ['inRow', 'contextmenu'] as any,
109
+ actionFunc: async (actionData: any) => {
110
+ const profile = actionData.item as interfaces.data.ISecurityProfile;
111
+ await this.showEditProfileDialog(profile);
110
112
  },
111
113
  },
112
114
  {
113
115
  name: 'Delete',
114
116
  iconName: 'lucide:trash2',
115
- type: ['contextmenu' as const],
116
- actionFunc: async (profile: interfaces.data.ISecurityProfile) => {
117
+ type: ['inRow', 'contextmenu'] as any,
118
+ actionFunc: async (actionData: any) => {
119
+ const profile = actionData.item as interfaces.data.ISecurityProfile;
117
120
  await this.deleteProfile(profile);
118
121
  },
119
122
  },
@@ -123,7 +126,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
123
126
  `;
124
127
  }
125
128
 
126
- private async showCreateProfileDialog(table: any) {
129
+ private async showCreateProfileDialog() {
127
130
  const { DeesModal } = await import('@design.estate/dees-catalog');
128
131
  DeesModal.createAndShow({
129
132
  heading: 'Create Security Profile',
@@ -137,10 +140,12 @@ export class OpsViewSecurityProfiles extends DeesElement {
137
140
  </dees-form>
138
141
  `,
139
142
  menuOptions: [
143
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
140
144
  {
141
145
  name: 'Create',
142
146
  action: async (modalArg: any) => {
143
- const form = modalArg.shadowRoot!.querySelector('dees-form');
147
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
148
+ if (!form) return;
144
149
  const data = await form.collectFormData();
145
150
  const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : [];
146
151
  const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : [];
@@ -158,12 +163,11 @@ export class OpsViewSecurityProfiles extends DeesElement {
158
163
  modalArg.destroy();
159
164
  },
160
165
  },
161
- { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
162
166
  ],
163
167
  });
164
168
  }
165
169
 
166
- private async showEditProfileDialog(profile: interfaces.data.ISecurityProfile, table: any) {
170
+ private async showEditProfileDialog(profile: interfaces.data.ISecurityProfile) {
167
171
  const { DeesModal } = await import('@design.estate/dees-catalog');
168
172
  DeesModal.createAndShow({
169
173
  heading: `Edit Profile: ${profile.name}`,
@@ -177,10 +181,12 @@ export class OpsViewSecurityProfiles extends DeesElement {
177
181
  </dees-form>
178
182
  `,
179
183
  menuOptions: [
184
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
180
185
  {
181
186
  name: 'Save',
182
187
  action: async (modalArg: any) => {
183
- const form = modalArg.shadowRoot!.querySelector('dees-form');
188
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
189
+ if (!form) return;
184
190
  const data = await form.collectFormData();
185
191
  const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : [];
186
192
  const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : [];
@@ -199,7 +205,6 @@ export class OpsViewSecurityProfiles extends DeesElement {
199
205
  modalArg.destroy();
200
206
  },
201
207
  },
202
- { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
203
208
  ],
204
209
  });
205
210
  }
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)