@serve.zone/dcrouter 6.7.0 → 6.9.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.
@@ -149,6 +149,17 @@ let OpsViewRemoteIngress = (() => {
149
149
  background: ${cssManager.bdTheme('#eff6ff', '#172554')};
150
150
  color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
151
151
  }
152
+
153
+ .portBadge.manual {
154
+ background: ${cssManager.bdTheme('#eff6ff', '#172554')};
155
+ color: ${cssManager.bdTheme('#1e40af', '#60a5fa')};
156
+ }
157
+
158
+ .portBadge.derived {
159
+ background: ${cssManager.bdTheme('#ecfdf5', '#022c22')};
160
+ color: ${cssManager.bdTheme('#047857', '#34d399')};
161
+ border: 1px dashed ${cssManager.bdTheme('#6ee7b7', '#065f46')};
162
+ }
152
163
  `,
153
164
  ];
154
165
  render() {
@@ -235,7 +246,8 @@ let OpsViewRemoteIngress = (() => {
235
246
  content: html `
236
247
  <dees-form>
237
248
  <dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
238
- <dees-input-text .key=${'listenPorts'} .label=${'Listen Ports (comma-separated, auto-derived if empty)'}></dees-input-text>
249
+ <dees-input-text .key=${'listenPorts'} .label=${'Additional Manual Ports (comma-separated, optional)'}></dees-input-text>
250
+ <dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${true}></dees-input-checkbox>
239
251
  <dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
240
252
  </dees-form>
241
253
  `,
@@ -260,10 +272,11 @@ let OpsViewRemoteIngress = (() => {
260
272
  const listenPorts = portsStr
261
273
  ? portsStr.split(',').map((p) => parseInt(p.trim(), 10)).filter((p) => !isNaN(p))
262
274
  : undefined;
275
+ const autoDerivePorts = formData.autoDerivePorts !== false;
263
276
  const tags = formData.tags
264
277
  ? formData.tags.split(',').map((t) => t.trim()).filter(Boolean)
265
278
  : undefined;
266
- await appstate.remoteIngressStatePart.dispatchAction(appstate.createRemoteIngressAction, { name, listenPorts, tags });
279
+ await appstate.remoteIngressStatePart.dispatchAction(appstate.createRemoteIngressAction, { name, listenPorts, autoDerivePorts, tags });
267
280
  await modalArg.destroy();
268
281
  },
269
282
  },
@@ -291,6 +304,59 @@ let OpsViewRemoteIngress = (() => {
291
304
  await appstate.remoteIngressStatePart.dispatchAction(appstate.toggleRemoteIngressAction, { id: edge.id, enabled: false });
292
305
  },
293
306
  },
307
+ {
308
+ name: 'Edit',
309
+ iconName: 'lucide:pencil',
310
+ type: ['inRow', 'contextmenu'],
311
+ actionFunc: async (actionData) => {
312
+ const edge = actionData.item;
313
+ const { DeesModal } = await import('@design.estate/dees-catalog');
314
+ await DeesModal.createAndShow({
315
+ heading: `Edit Edge: ${edge.name}`,
316
+ content: html `
317
+ <dees-form>
318
+ <dees-input-text .key=${'name'} .label=${'Name'} .value=${edge.name}></dees-input-text>
319
+ <dees-input-text .key=${'listenPorts'} .label=${'Manual Ports (comma-separated)'} .value=${(edge.listenPorts || []).join(', ')}></dees-input-text>
320
+ <dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${edge.autoDerivePorts !== false}></dees-input-checkbox>
321
+ <dees-input-text .key=${'tags'} .label=${'Tags (comma-separated)'} .value=${(edge.tags || []).join(', ')}></dees-input-text>
322
+ </dees-form>
323
+ `,
324
+ menuOptions: [
325
+ {
326
+ name: 'Cancel',
327
+ iconName: 'lucide:x',
328
+ action: async (modalArg) => await modalArg.destroy(),
329
+ },
330
+ {
331
+ name: 'Save',
332
+ iconName: 'lucide:check',
333
+ action: async (modalArg) => {
334
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
335
+ if (!form)
336
+ return;
337
+ const formData = await form.collectFormData();
338
+ const portsStr = formData.listenPorts?.trim();
339
+ const listenPorts = portsStr
340
+ ? portsStr.split(',').map((p) => parseInt(p.trim(), 10)).filter((p) => !isNaN(p))
341
+ : [];
342
+ const autoDerivePorts = formData.autoDerivePorts !== false;
343
+ const tags = formData.tags
344
+ ? formData.tags.split(',').map((t) => t.trim()).filter(Boolean)
345
+ : [];
346
+ await appstate.remoteIngressStatePart.dispatchAction(appstate.updateRemoteIngressAction, {
347
+ id: edge.id,
348
+ name: formData.name || edge.name,
349
+ listenPorts,
350
+ autoDerivePorts,
351
+ tags,
352
+ });
353
+ await modalArg.destroy();
354
+ },
355
+ },
356
+ ],
357
+ });
358
+ },
359
+ },
294
360
  {
295
361
  name: 'Regenerate Secret',
296
362
  iconName: 'lucide:key',
@@ -332,13 +398,12 @@ let OpsViewRemoteIngress = (() => {
332
398
  return status?.publicIp || '-';
333
399
  }
334
400
  getPortsHtml(edge) {
335
- const hasManualPorts = edge.listenPorts && edge.listenPorts.length > 0;
336
- const ports = hasManualPorts ? edge.listenPorts : (edge.effectiveListenPorts || []);
337
- const isAuto = !hasManualPorts && ports.length > 0;
338
- if (ports.length === 0) {
401
+ const manualPorts = edge.manualPorts || [];
402
+ const derivedPorts = edge.derivedPorts || [];
403
+ if (manualPorts.length === 0 && derivedPorts.length === 0) {
339
404
  return html `<span style="color: var(--text-muted, #6b7280); font-size: 12px;">none</span>`;
340
405
  }
341
- return html `<div class="portsDisplay">${ports.map(p => html `<span class="portBadge">${p}</span>`)}${isAuto ? html `<span style="font-size: 11px; color: var(--text-muted, #6b7280); align-self: center;">(auto)</span>` : ''}</div>`;
406
+ return html `<div class="portsDisplay">${manualPorts.map(p => html `<span class="portBadge manual">${p}</span>`)}${derivedPorts.map(p => html `<span class="portBadge derived">${p}</span>`)}${derivedPorts.length > 0 ? html `<span style="font-size: 11px; color: var(--text-muted, #6b7280); align-self: center;">(auto)</span>` : ''}</div>`;
342
407
  }
343
408
  getEdgeTunnelCount(edgeId) {
344
409
  const status = this.getEdgeStatus(edgeId);
@@ -362,4 +427,4 @@ let OpsViewRemoteIngress = (() => {
362
427
  return OpsViewRemoteIngress = _classThis;
363
428
  })();
364
429
  export { OpsViewRemoteIngress };
365
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctcmVtb3RlaW5ncmVzcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3dlYi9lbGVtZW50cy9vcHMtdmlldy1yZW1vdGVpbmdyZXNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEVBQ0wsV0FBVyxFQUNYLElBQUksRUFDSixhQUFhLEVBRWIsR0FBRyxFQUNILEtBQUssRUFDTCxVQUFVLEdBQ1gsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEtBQUssUUFBUSxNQUFNLGdCQUFnQixDQUFDO0FBQzNDLE9BQU8sS0FBSyxVQUFVLE1BQU0sbUNBQW1DLENBQUM7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzlDLE9BQU8sRUFBbUIsTUFBTSw2QkFBNkIsQ0FBQztJQVNqRCxvQkFBb0I7NEJBRGhDLGFBQWEsQ0FBQyx3QkFBd0IsQ0FBQzs7OztzQkFDRSxXQUFXOzs7O29DQUFuQixTQUFRLFdBQVc7Ozs7bUNBQ2xELEtBQUssRUFBRTtZQUNSLDBLQUFTLE9BQU8sNkJBQVAsT0FBTyx5RkFBNEU7WUFGOUYsNktBZ1VDOzs7O1FBOVRDLDJFQUFpRCxRQUFRLENBQUMsc0JBQXNCLENBQUMsUUFBUSxFQUFFLEVBQUM7UUFBNUYsSUFBUyxPQUFPLDZDQUE0RTtRQUE1RixJQUFTLE9BQU8sbURBQTRFO1FBRTVGO1lBQ0UsS0FBSyxFQUFFLENBQUM7O1lBQ1IsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLHNCQUFzQixDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQyxRQUFRLEVBQUUsRUFBRTtnQkFDdkUsSUFBSSxDQUFDLE9BQU8sR0FBRyxRQUFRLENBQUM7WUFDMUIsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUNoQztRQUVELEtB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
430
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctcmVtb3RlaW5ncmVzcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3dlYi9lbGVtZW50cy9vcHMtdmlldy1yZW1vdGVpbmdyZXNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxPQUFPLEVBQ0wsV0FBVyxFQUNYLElBQUksRUFDSixhQUFhLEVBRWIsR0FBRyxFQUNILEtBQUssRUFDTCxVQUFVLEdBQ1gsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEtBQUssUUFBUSxNQUFNLGdCQUFnQixDQUFDO0FBQzNDLE9BQU8sS0FBSyxVQUFVLE1BQU0sbUNBQW1DLENBQUM7QUFDaEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzlDLE9BQU8sRUFBbUIsTUFBTSw2QkFBNkIsQ0FBQztJQVNqRCxvQkFBb0I7NEJBRGhDLGFBQWEsQ0FBQyx3QkFBd0IsQ0FBQzs7OztzQkFDRSxXQUFXOzs7O29DQUFuQixTQUFRLFdBQVc7Ozs7bUNBQ2xELEtBQUssRUFBRTtZQUNSLDBLQUFTLE9BQU8sNkJBQVAsT0FBTyx5RkFBNEU7WUFGOUYsNktBbVlDOzs7O1FBallDLDJFQUFpRCxRQUFRLENBQUMsc0JBQXNCLENBQUMsUUFBUSxFQUFFLEVBQUM7UUFBNUYsSUFBUyxPQUFPLDZDQUE0RTtRQUE1RixJQUFTLE9BQU8sbURBQTRFO1FBRTVGO1lBQ0UsS0FBSyxFQUFFLENBQUM7O1lBQ1IsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLHNCQUFzQixDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQyxRQUFRLEVBQUUsRUFBRTtnQkFDdkUsSUFBSSxDQUFDLE9BQU8sR0FBRyxRQUFRLENBQUM7WUFDMUIsQ0FBQyxDQUFDLENBQUM7WUFDSCxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUNoQztRQUVELEtB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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "6.7.0",
4
+ "version": "6.9.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '6.7.0',
6
+ version: '6.9.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -42,6 +42,36 @@ export class CertificateHandler {
42
42
  }
43
43
  )
44
44
  );
45
+
46
+ // Delete certificate
47
+ this.typedrouter.addTypedHandler(
48
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteCertificate>(
49
+ 'deleteCertificate',
50
+ async (dataArg) => {
51
+ return this.deleteCertificate(dataArg.domain);
52
+ }
53
+ )
54
+ );
55
+
56
+ // Export certificate
57
+ this.typedrouter.addTypedHandler(
58
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportCertificate>(
59
+ 'exportCertificate',
60
+ async (dataArg) => {
61
+ return this.exportCertificate(dataArg.domain);
62
+ }
63
+ )
64
+ );
65
+
66
+ // Import certificate
67
+ this.typedrouter.addTypedHandler(
68
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ImportCertificate>(
69
+ 'importCertificate',
70
+ async (dataArg) => {
71
+ return this.importCertificate(dataArg.cert);
72
+ }
73
+ )
74
+ );
45
75
  }
46
76
 
47
77
  /**
@@ -324,4 +354,154 @@ export class CertificateHandler {
324
354
 
325
355
  return { success: false, message: `No routes found for domain '${domain}'` };
326
356
  }
357
+
358
+ /**
359
+ * Delete certificate data for a domain from storage
360
+ */
361
+ private async deleteCertificate(domain: string): Promise<{ success: boolean; message?: string }> {
362
+ const dcRouter = this.opsServerRef.dcRouterRef;
363
+ const cleanDomain = domain.replace(/^\*\.?/, '');
364
+
365
+ // Delete from all known storage paths
366
+ const paths = [
367
+ `/proxy-certs/${domain}`,
368
+ `/proxy-certs/${cleanDomain}`,
369
+ `/certs/${cleanDomain}`,
370
+ ];
371
+
372
+ for (const path of paths) {
373
+ try {
374
+ await dcRouter.storageManager.delete(path);
375
+ } catch {
376
+ // Path may not exist — ignore
377
+ }
378
+ }
379
+
380
+ // Clear from in-memory status map
381
+ dcRouter.certificateStatusMap.delete(domain);
382
+
383
+ // Clear backoff info
384
+ if (dcRouter.certProvisionScheduler) {
385
+ await dcRouter.certProvisionScheduler.clearBackoff(domain);
386
+ }
387
+
388
+ return { success: true, message: `Certificate data deleted for '${domain}'` };
389
+ }
390
+
391
+ /**
392
+ * Export certificate data for a domain as ICert-shaped JSON
393
+ */
394
+ private async exportCertificate(domain: string): Promise<{
395
+ success: boolean;
396
+ cert?: {
397
+ id: string;
398
+ domainName: string;
399
+ created: number;
400
+ validUntil: number;
401
+ privateKey: string;
402
+ publicKey: string;
403
+ csr: string;
404
+ };
405
+ message?: string;
406
+ }> {
407
+ const dcRouter = this.opsServerRef.dcRouterRef;
408
+ const cleanDomain = domain.replace(/^\*\.?/, '');
409
+
410
+ // Try SmartAcme /certs/ path first (has full ICert fields)
411
+ let certData = await dcRouter.storageManager.getJSON(`/certs/${cleanDomain}`);
412
+ if (certData && certData.publicKey && certData.privateKey) {
413
+ return {
414
+ success: true,
415
+ cert: {
416
+ id: certData.id || plugins.crypto.randomUUID(),
417
+ domainName: certData.domainName || domain,
418
+ created: certData.created || Date.now(),
419
+ validUntil: certData.validUntil || 0,
420
+ privateKey: certData.privateKey,
421
+ publicKey: certData.publicKey,
422
+ csr: certData.csr || '',
423
+ },
424
+ };
425
+ }
426
+
427
+ // Fallback: try /proxy-certs/ with original domain
428
+ certData = await dcRouter.storageManager.getJSON(`/proxy-certs/${domain}`);
429
+ if (!certData || !certData.publicKey) {
430
+ // Try with clean domain
431
+ certData = await dcRouter.storageManager.getJSON(`/proxy-certs/${cleanDomain}`);
432
+ }
433
+
434
+ if (certData && certData.publicKey && certData.privateKey) {
435
+ return {
436
+ success: true,
437
+ cert: {
438
+ id: plugins.crypto.randomUUID(),
439
+ domainName: domain,
440
+ created: certData.validFrom || Date.now(),
441
+ validUntil: certData.validUntil || 0,
442
+ privateKey: certData.privateKey,
443
+ publicKey: certData.publicKey,
444
+ csr: '',
445
+ },
446
+ };
447
+ }
448
+
449
+ return { success: false, message: `No certificate data found for '${domain}'` };
450
+ }
451
+
452
+ /**
453
+ * Import a certificate from ICert-shaped JSON
454
+ */
455
+ private async importCertificate(cert: {
456
+ id: string;
457
+ domainName: string;
458
+ created: number;
459
+ validUntil: number;
460
+ privateKey: string;
461
+ publicKey: string;
462
+ csr: string;
463
+ }): Promise<{ success: boolean; message?: string }> {
464
+ // Validate PEM content
465
+ if (!cert.publicKey || !cert.publicKey.includes('-----BEGIN CERTIFICATE-----')) {
466
+ return { success: false, message: 'Invalid publicKey: must contain a PEM-encoded certificate' };
467
+ }
468
+ if (!cert.privateKey || !cert.privateKey.includes('-----BEGIN')) {
469
+ return { success: false, message: 'Invalid privateKey: must contain a PEM-encoded key' };
470
+ }
471
+
472
+ const dcRouter = this.opsServerRef.dcRouterRef;
473
+ const cleanDomain = cert.domainName.replace(/^\*\.?/, '');
474
+
475
+ // Save to /certs/ (SmartAcme-compatible path)
476
+ await dcRouter.storageManager.setJSON(`/certs/${cleanDomain}`, {
477
+ id: cert.id,
478
+ domainName: cert.domainName,
479
+ created: cert.created,
480
+ validUntil: cert.validUntil,
481
+ privateKey: cert.privateKey,
482
+ publicKey: cert.publicKey,
483
+ csr: cert.csr || '',
484
+ });
485
+
486
+ // Also save to /proxy-certs/ (proxy-cert format)
487
+ await dcRouter.storageManager.setJSON(`/proxy-certs/${cert.domainName}`, {
488
+ domain: cert.domainName,
489
+ publicKey: cert.publicKey,
490
+ privateKey: cert.privateKey,
491
+ ca: undefined,
492
+ validUntil: cert.validUntil,
493
+ validFrom: cert.created,
494
+ });
495
+
496
+ // Update in-memory status map
497
+ dcRouter.certificateStatusMap.set(cert.domainName, {
498
+ status: 'valid',
499
+ source: 'static',
500
+ expiryDate: cert.validUntil ? new Date(cert.validUntil).toISOString() : undefined,
501
+ issuedAt: cert.created ? new Date(cert.created).toISOString() : undefined,
502
+ routeNames: [],
503
+ });
504
+
505
+ return { success: true, message: `Certificate imported for '${cert.domainName}'` };
506
+ }
327
507
  }
@@ -20,12 +20,17 @@ export class RemoteIngressHandler {
20
20
  if (!manager) {
21
21
  return { edges: [] };
22
22
  }
23
- // Return edges without secrets, enriched with effective listen ports
24
- const edges = manager.getAllEdges().map((e) => ({
25
- ...e,
26
- secret: '********', // Never expose secrets via API
27
- effectiveListenPorts: manager.getEffectiveListenPorts(e),
28
- }));
23
+ // Return edges without secrets, enriched with effective listen ports and breakdown
24
+ const edges = manager.getAllEdges().map((e) => {
25
+ const breakdown = manager.getPortBreakdown(e);
26
+ return {
27
+ ...e,
28
+ secret: '********', // Never expose secrets via API
29
+ effectiveListenPorts: manager.getEffectiveListenPorts(e),
30
+ manualPorts: breakdown.manual,
31
+ derivedPorts: breakdown.derived,
32
+ };
33
+ });
29
34
  return { edges };
30
35
  },
31
36
  ),
@@ -50,6 +55,7 @@ export class RemoteIngressHandler {
50
55
  dataArg.name,
51
56
  dataArg.listenPorts || [],
52
57
  dataArg.tags,
58
+ dataArg.autoDerivePorts ?? true,
53
59
  );
54
60
 
55
61
  // Sync allowed edges with the hub
@@ -102,6 +108,7 @@ export class RemoteIngressHandler {
102
108
  const edge = await manager.updateEdge(dataArg.id, {
103
109
  name: dataArg.name,
104
110
  listenPorts: dataArg.listenPorts,
111
+ autoDerivePorts: dataArg.autoDerivePorts,
105
112
  enabled: dataArg.enabled,
106
113
  tags: dataArg.tags,
107
114
  });
@@ -115,7 +122,17 @@ export class RemoteIngressHandler {
115
122
  await tunnelManager.syncAllowedEdges();
116
123
  }
117
124
 
118
- return { success: true, edge: { ...edge, secret: '********' } };
125
+ const breakdown = manager.getPortBreakdown(edge);
126
+ return {
127
+ success: true,
128
+ edge: {
129
+ ...edge,
130
+ secret: '********',
131
+ effectiveListenPorts: manager.getEffectiveListenPorts(edge),
132
+ manualPorts: breakdown.manual,
133
+ derivedPorts: breakdown.derived,
134
+ },
135
+ };
119
136
  },
120
137
  ),
121
138
  );
@@ -47,6 +47,11 @@ export class RemoteIngressManager {
47
47
  for (const key of keys) {
48
48
  const edge = await this.storageManager.getJSON<IRemoteIngress>(key);
49
49
  if (edge) {
50
+ // Migration: old edges without autoDerivePorts default to true
51
+ if ((edge as any).autoDerivePorts === undefined) {
52
+ edge.autoDerivePorts = true;
53
+ await this.storageManager.setJSON(key, edge);
54
+ }
50
55
  this.edges.set(edge.id, edge);
51
56
  }
52
57
  }
@@ -91,13 +96,28 @@ export class RemoteIngressManager {
91
96
 
92
97
  /**
93
98
  * Get the effective listen ports for an edge.
94
- * Returns manual listenPorts if non-empty, otherwise derives ports from tagged routes.
99
+ * Manual ports are always included. Auto-derived ports are added (union) when autoDerivePorts is true.
95
100
  */
96
101
  public getEffectiveListenPorts(edge: IRemoteIngress): number[] {
97
- if (edge.listenPorts && edge.listenPorts.length > 0) {
98
- return edge.listenPorts;
99
- }
100
- return this.derivePortsForEdge(edge.id, edge.tags);
102
+ const manualPorts = edge.listenPorts || [];
103
+ const shouldDerive = edge.autoDerivePorts !== false;
104
+ if (!shouldDerive) return [...manualPorts].sort((a, b) => a - b);
105
+ const derivedPorts = this.derivePortsForEdge(edge.id, edge.tags);
106
+ return [...new Set([...manualPorts, ...derivedPorts])].sort((a, b) => a - b);
107
+ }
108
+
109
+ /**
110
+ * Get manual and derived port breakdown for an edge (used in API responses).
111
+ * Derived ports exclude any ports already present in the manual list.
112
+ */
113
+ public getPortBreakdown(edge: IRemoteIngress): { manual: number[]; derived: number[] } {
114
+ const manual = edge.listenPorts || [];
115
+ const shouldDerive = edge.autoDerivePorts !== false;
116
+ if (!shouldDerive) return { manual, derived: [] };
117
+ const manualSet = new Set(manual);
118
+ const allDerived = this.derivePortsForEdge(edge.id, edge.tags);
119
+ const derived = allDerived.filter((p) => !manualSet.has(p));
120
+ return { manual, derived };
101
121
  }
102
122
 
103
123
  /**
@@ -107,6 +127,7 @@ export class RemoteIngressManager {
107
127
  name: string,
108
128
  listenPorts: number[] = [],
109
129
  tags?: string[],
130
+ autoDerivePorts: boolean = true,
110
131
  ): Promise<IRemoteIngress> {
111
132
  const id = plugins.uuid.v4();
112
133
  const secret = plugins.crypto.randomBytes(32).toString('hex');
@@ -118,6 +139,7 @@ export class RemoteIngressManager {
118
139
  secret,
119
140
  listenPorts,
120
141
  enabled: true,
142
+ autoDerivePorts,
121
143
  tags: tags || [],
122
144
  createdAt: now,
123
145
  updatedAt: now,
@@ -150,6 +172,7 @@ export class RemoteIngressManager {
150
172
  updates: {
151
173
  name?: string;
152
174
  listenPorts?: number[];
175
+ autoDerivePorts?: boolean;
153
176
  enabled?: boolean;
154
177
  tags?: string[];
155
178
  },
@@ -161,6 +184,7 @@ export class RemoteIngressManager {
161
184
 
162
185
  if (updates.name !== undefined) edge.name = updates.name;
163
186
  if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
187
+ if (updates.autoDerivePorts !== undefined) edge.autoDerivePorts = updates.autoDerivePorts;
164
188
  if (updates.enabled !== undefined) edge.enabled = updates.enabled;
165
189
  if (updates.tags !== undefined) edge.tags = updates.tags;
166
190
  edge.updatedAt = Date.now();
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '6.7.0',
6
+ version: '6.9.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -780,6 +780,80 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
780
780
  }
781
781
  );
782
782
 
783
+ export const deleteCertificateAction = certificateStatePart.createAction<string>(
784
+ async (statePartArg, domain) => {
785
+ const context = getActionContext();
786
+ const currentState = statePartArg.getState();
787
+
788
+ try {
789
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
790
+ interfaces.requests.IReq_DeleteCertificate
791
+ >('/typedrequest', 'deleteCertificate');
792
+
793
+ await request.fire({
794
+ identity: context.identity,
795
+ domain,
796
+ });
797
+
798
+ // Re-fetch overview after deletion
799
+ await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
800
+ return statePartArg.getState();
801
+ } catch (error) {
802
+ return {
803
+ ...currentState,
804
+ error: error instanceof Error ? error.message : 'Failed to delete certificate',
805
+ };
806
+ }
807
+ }
808
+ );
809
+
810
+ export const importCertificateAction = certificateStatePart.createAction<{
811
+ id: string;
812
+ domainName: string;
813
+ created: number;
814
+ validUntil: number;
815
+ privateKey: string;
816
+ publicKey: string;
817
+ csr: string;
818
+ }>(
819
+ async (statePartArg, cert) => {
820
+ const context = getActionContext();
821
+ const currentState = statePartArg.getState();
822
+
823
+ try {
824
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
825
+ interfaces.requests.IReq_ImportCertificate
826
+ >('/typedrequest', 'importCertificate');
827
+
828
+ await request.fire({
829
+ identity: context.identity,
830
+ cert,
831
+ });
832
+
833
+ // Re-fetch overview after import
834
+ await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
835
+ return statePartArg.getState();
836
+ } catch (error) {
837
+ return {
838
+ ...currentState,
839
+ error: error instanceof Error ? error.message : 'Failed to import certificate',
840
+ };
841
+ }
842
+ }
843
+ );
844
+
845
+ export async function fetchCertificateExport(domain: string) {
846
+ const context = getActionContext();
847
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
848
+ interfaces.requests.IReq_ExportCertificate
849
+ >('/typedrequest', 'exportCertificate');
850
+
851
+ return request.fire({
852
+ identity: context.identity,
853
+ domain,
854
+ });
855
+ }
856
+
783
857
  // ============================================================================
784
858
  // Remote Ingress Actions
785
859
  // ============================================================================
@@ -822,6 +896,7 @@ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(asyn
822
896
  export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
823
897
  name: string;
824
898
  listenPorts?: number[];
899
+ autoDerivePorts?: boolean;
825
900
  tags?: string[];
826
901
  }>(async (statePartArg, dataArg) => {
827
902
  const context = getActionContext();
@@ -836,6 +911,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
836
911
  identity: context.identity,
837
912
  name: dataArg.name,
838
913
  listenPorts: dataArg.listenPorts,
914
+ autoDerivePorts: dataArg.autoDerivePorts,
839
915
  tags: dataArg.tags,
840
916
  });
841
917
 
@@ -883,6 +959,40 @@ export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<str
883
959
  }
884
960
  );
885
961
 
962
+ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
963
+ id: string;
964
+ name?: string;
965
+ listenPorts?: number[];
966
+ autoDerivePorts?: boolean;
967
+ tags?: string[];
968
+ }>(async (statePartArg, dataArg) => {
969
+ const context = getActionContext();
970
+ const currentState = statePartArg.getState();
971
+
972
+ try {
973
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
974
+ interfaces.requests.IReq_UpdateRemoteIngress
975
+ >('/typedrequest', 'updateRemoteIngress');
976
+
977
+ await request.fire({
978
+ identity: context.identity,
979
+ id: dataArg.id,
980
+ name: dataArg.name,
981
+ listenPorts: dataArg.listenPorts,
982
+ autoDerivePorts: dataArg.autoDerivePorts,
983
+ tags: dataArg.tags,
984
+ });
985
+
986
+ await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
987
+ return statePartArg.getState();
988
+ } catch (error) {
989
+ return {
990
+ ...currentState,
991
+ error: error instanceof Error ? error.message : 'Failed to update edge',
992
+ };
993
+ }
994
+ });
995
+
886
996
  export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.createAction<string>(
887
997
  async (statePartArg, edgeId) => {
888
998
  const context = getActionContext();