From 5202c2ea27d7f69f3dd2a1e7cbcf1ebf8655aff1 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 2 Apr 2026 18:49:52 +0000 Subject: [PATCH] fix(ops-ui): improve operations table actions and modal form handling for profiles and network targets --- changelog.md | 8 +++++ test_watch/devserver.ts | 3 +- ts/00_commitinfo_data.ts | 2 +- ts_web/00_commitinfo_data.ts | 2 +- ts_web/elements/ops-view-networktargets.ts | 31 ++++++++++++-------- ts_web/elements/ops-view-securityprofiles.ts | 31 ++++++++++++-------- 6 files changed, 47 insertions(+), 30 deletions(-) diff --git a/changelog.md b/changelog.md index fdf47f4..6c7372a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-04-02 - 12.2.6 - fix(ops-ui) +improve operations table actions and modal form handling for profiles and network targets + +- adds section headings for the Security Profiles and Network Targets views +- updates edit and delete actions to support in-row table actions in addition to context menus +- makes create and edit dialogs query forms safely from modal content and adds early returns when forms are unavailable +- enables the database configuration in the development watch server + ## 2026-04-02 - 12.2.5 - fix(dcrouter) sync allowed tunnel edges when merged routes change diff --git a/test_watch/devserver.ts b/test_watch/devserver.ts index 8535d73..266cb53 100644 --- a/test_watch/devserver.ts +++ b/test_watch/devserver.ts @@ -49,8 +49,7 @@ const devRouter = new DcRouter({ { clientId: 'admin-desktop', serverDefinedClientTags: ['admin'], description: 'Admin workstation' }, ], }, - // Disable db/mongo for dev - dbConfig: { enabled: false }, + dbConfig: { enabled: true }, }); console.log('Starting DcRouter in development mode...'); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 8fa92a0..a135352 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: '12.2.5', + version: '12.2.6', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 8fa92a0..a135352 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: '12.2.5', + version: '12.2.6', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/elements/ops-view-networktargets.ts b/ts_web/elements/ops-view-networktargets.ts index 5f214b3..27e2c24 100644 --- a/ts_web/elements/ops-view-networktargets.ts +++ b/ts_web/elements/ops-view-networktargets.ts @@ -64,6 +64,7 @@ export class OpsViewNetworkTargets extends DeesElement { ]; return html` + Network Targets
{ - await this.showCreateTargetDialog(table); + actionFunc: async () => { + await this.showCreateTargetDialog(); }, }, { @@ -96,16 +97,18 @@ export class OpsViewNetworkTargets extends DeesElement { { name: 'Edit', iconName: 'lucide:pencil', - type: ['contextmenu' as const], - actionFunc: async (target: interfaces.data.INetworkTarget, table: any) => { - await this.showEditTargetDialog(target, table); + type: ['inRow', 'contextmenu'] as any, + actionFunc: async (actionData: any) => { + const target = actionData.item as interfaces.data.INetworkTarget; + await this.showEditTargetDialog(target); }, }, { name: 'Delete', iconName: 'lucide:trash2', - type: ['contextmenu' as const], - actionFunc: async (target: interfaces.data.INetworkTarget) => { + type: ['inRow', 'contextmenu'] as any, + actionFunc: async (actionData: any) => { + const target = actionData.item as interfaces.data.INetworkTarget; await this.deleteTarget(target); }, }, @@ -115,7 +118,7 @@ export class OpsViewNetworkTargets extends DeesElement { `; } - private async showCreateTargetDialog(table: any) { + private async showCreateTargetDialog() { const { DeesModal } = await import('@design.estate/dees-catalog'); DeesModal.createAndShow({ heading: 'Create Network Target', @@ -128,10 +131,12 @@ export class OpsViewNetworkTargets extends DeesElement { `, menuOptions: [ + { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, { name: 'Create', action: async (modalArg: any) => { - const form = modalArg.shadowRoot!.querySelector('dees-form'); + const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form'); + if (!form) return; const data = await form.collectFormData(); await appstate.profilesTargetsStatePart.dispatchAction(appstate.createTargetAction, { @@ -143,12 +148,11 @@ export class OpsViewNetworkTargets extends DeesElement { modalArg.destroy(); }, }, - { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); } - private async showEditTargetDialog(target: interfaces.data.INetworkTarget, table: any) { + private async showEditTargetDialog(target: interfaces.data.INetworkTarget) { const hostStr = Array.isArray(target.host) ? target.host.join(', ') : target.host; const { DeesModal } = await import('@design.estate/dees-catalog'); @@ -163,10 +167,12 @@ export class OpsViewNetworkTargets extends DeesElement { `, menuOptions: [ + { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, { name: 'Save', action: async (modalArg: any) => { - const form = modalArg.shadowRoot!.querySelector('dees-form'); + const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form'); + if (!form) return; const data = await form.collectFormData(); await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateTargetAction, { @@ -179,7 +185,6 @@ export class OpsViewNetworkTargets extends DeesElement { modalArg.destroy(); }, }, - { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); } diff --git a/ts_web/elements/ops-view-securityprofiles.ts b/ts_web/elements/ops-view-securityprofiles.ts index f7f8b6d..0fede12 100644 --- a/ts_web/elements/ops-view-securityprofiles.ts +++ b/ts_web/elements/ops-view-securityprofiles.ts @@ -64,6 +64,7 @@ export class OpsViewSecurityProfiles extends DeesElement { ]; return html` + Security Profiles
{ - await this.showCreateProfileDialog(table); + actionFunc: async () => { + await this.showCreateProfileDialog(); }, }, { @@ -104,16 +105,18 @@ export class OpsViewSecurityProfiles extends DeesElement { { name: 'Edit', iconName: 'lucide:pencil', - type: ['contextmenu' as const], - actionFunc: async (profile: interfaces.data.ISecurityProfile, table: any) => { - await this.showEditProfileDialog(profile, table); + type: ['inRow', 'contextmenu'] as any, + actionFunc: async (actionData: any) => { + const profile = actionData.item as interfaces.data.ISecurityProfile; + await this.showEditProfileDialog(profile); }, }, { name: 'Delete', iconName: 'lucide:trash2', - type: ['contextmenu' as const], - actionFunc: async (profile: interfaces.data.ISecurityProfile) => { + type: ['inRow', 'contextmenu'] as any, + actionFunc: async (actionData: any) => { + const profile = actionData.item as interfaces.data.ISecurityProfile; await this.deleteProfile(profile); }, }, @@ -123,7 +126,7 @@ export class OpsViewSecurityProfiles extends DeesElement { `; } - private async showCreateProfileDialog(table: any) { + private async showCreateProfileDialog() { const { DeesModal } = await import('@design.estate/dees-catalog'); DeesModal.createAndShow({ heading: 'Create Security Profile', @@ -137,10 +140,12 @@ export class OpsViewSecurityProfiles extends DeesElement { `, menuOptions: [ + { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, { name: 'Create', action: async (modalArg: any) => { - const form = modalArg.shadowRoot!.querySelector('dees-form'); + const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form'); + if (!form) return; const data = await form.collectFormData(); const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : []; const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : []; @@ -158,12 +163,11 @@ export class OpsViewSecurityProfiles extends DeesElement { modalArg.destroy(); }, }, - { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); } - private async showEditProfileDialog(profile: interfaces.data.ISecurityProfile, table: any) { + private async showEditProfileDialog(profile: interfaces.data.ISecurityProfile) { const { DeesModal } = await import('@design.estate/dees-catalog'); DeesModal.createAndShow({ heading: `Edit Profile: ${profile.name}`, @@ -177,10 +181,12 @@ export class OpsViewSecurityProfiles extends DeesElement { `, menuOptions: [ + { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, { name: 'Save', action: async (modalArg: any) => { - const form = modalArg.shadowRoot!.querySelector('dees-form'); + const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form'); + if (!form) return; const data = await form.collectFormData(); const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : []; const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : []; @@ -199,7 +205,6 @@ export class OpsViewSecurityProfiles extends DeesElement { modalArg.destroy(); }, }, - { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() }, ], }); }