diff --git a/changelog.md b/changelog.md
index e050ecb..c84f298 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,14 @@
# Changelog
+## 2026-02-17 - 6.7.0 - feat(remote-ingress)
+Support auto-derived effective listen ports, make listenPorts optional, add toggle action and refine remote ingress creation/management UI
+
+- Add effectiveListenPorts?: number[] to IRemoteIngress interface (present in API responses)
+- Make createRemoteIngressAction.listenPorts optional and update creation modal to allow empty ports (auto-derived)
+- Add toggleRemoteIngressAction to enable/disable remote ingress edges and wire up Enable/Disable row/context-menu actions
+- Update getPortsHtml to prefer manual listenPorts, fall back to effectiveListenPorts, show '(auto)' when derived and 'none' when no ports
+- Standardize UI actions to use inRow/contextmenu and actionFunc signatures; update create modal to use explicit Cancel/Create menu options and collect form data programmatically
+
## 2026-02-17 - 6.6.1 - fix(icons)
standardize icon identifiers to lucide-prefixed names across operational views
diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts
index 1928b36..dba153b 100644
--- a/ts/00_commitinfo_data.ts
+++ b/ts/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
- version: '6.6.1',
+ version: '6.7.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}
diff --git a/ts_interfaces/data/remoteingress.ts b/ts_interfaces/data/remoteingress.ts
index 93b8b32..3e27e0f 100644
--- a/ts_interfaces/data/remoteingress.ts
+++ b/ts_interfaces/data/remoteingress.ts
@@ -12,6 +12,8 @@ export interface IRemoteIngress {
tags?: string[];
createdAt: number;
updatedAt: number;
+ /** Effective ports derived from route configs — only present in API responses. */
+ effectiveListenPorts?: number[];
}
/**
diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts
index 1928b36..dba153b 100644
--- a/ts_web/00_commitinfo_data.ts
+++ b/ts_web/00_commitinfo_data.ts
@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
- version: '6.6.1',
+ version: '6.7.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}
diff --git a/ts_web/appstate.ts b/ts_web/appstate.ts
index 278ced2..916370f 100644
--- a/ts_web/appstate.ts
+++ b/ts_web/appstate.ts
@@ -821,7 +821,7 @@ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(asyn
export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
name: string;
- listenPorts: number[];
+ listenPorts?: number[];
tags?: string[];
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
@@ -924,6 +924,34 @@ export const clearNewEdgeSecretAction = remoteIngressStatePart.createAction(
}
);
+export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
+ id: string;
+ enabled: boolean;
+}>(async (statePartArg, dataArg) => {
+ const context = getActionContext();
+ const currentState = statePartArg.getState();
+
+ try {
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
+ interfaces.requests.IReq_UpdateRemoteIngress
+ >('/typedrequest', 'updateRemoteIngress');
+
+ await request.fire({
+ identity: context.identity,
+ id: dataArg.id,
+ enabled: dataArg.enabled,
+ });
+
+ await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
+ return statePartArg.getState();
+ } catch (error) {
+ return {
+ ...currentState,
+ error: error instanceof Error ? error.message : 'Failed to toggle edge',
+ };
+ }
+});
+
// Combined refresh action for efficient polling
async function dispatchCombinedRefreshAction() {
const context = getActionContext();
diff --git a/ts_web/elements/ops-view-remoteingress.ts b/ts_web/elements/ops-view-remoteingress.ts
index 14db9b0..522b710 100644
--- a/ts_web/elements/ops-view-remoteingress.ts
+++ b/ts_web/elements/ops-view-remoteingress.ts
@@ -187,7 +187,7 @@ export class OpsViewRemoteIngress extends DeesElement {
name: edge.name,
status: this.getEdgeStatusHtml(edge),
publicIp: this.getEdgePublicIp(edge.id),
- ports: this.getPortsHtml(edge.listenPorts),
+ ports: this.getPortsHtml(edge),
tunnels: this.getEdgeTunnelCount(edge.id),
lastHeartbeat: this.getLastHeartbeat(edge.id),
})}
@@ -198,42 +198,80 @@ export class OpsViewRemoteIngress extends DeesElement {
type: ['header'],
actionFunc: async () => {
const { DeesModal } = await import('@design.estate/dees-catalog');
- const result = await DeesModal.createAndShow({
+ const modal = await DeesModal.createAndShow({
heading: 'Create Edge Node',
content: html`