fix(ops-ui): improve operations table actions and modal form handling for profiles and network targets

This commit is contained in:
2026-04-02 18:49:52 +00:00
parent 6684dc43da
commit 5202c2ea27
6 changed files with 47 additions and 30 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # 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) ## 2026-04-02 - 12.2.5 - fix(dcrouter)
sync allowed tunnel edges when merged routes change sync allowed tunnel edges when merged routes change

View File

@@ -49,8 +49,7 @@ const devRouter = new DcRouter({
{ clientId: 'admin-desktop', serverDefinedClientTags: ['admin'], description: 'Admin workstation' }, { clientId: 'admin-desktop', serverDefinedClientTags: ['admin'], description: 'Admin workstation' },
], ],
}, },
// Disable db/mongo for dev dbConfig: { enabled: true },
dbConfig: { enabled: false },
}); });
console.log('Starting DcRouter in development mode...'); console.log('Starting DcRouter in development mode...');

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '12.2.5', version: '12.2.6',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '12.2.5', version: '12.2.6',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -64,6 +64,7 @@ export class OpsViewNetworkTargets extends DeesElement {
]; ];
return html` return html`
<ops-sectionheading>Network Targets</ops-sectionheading>
<div class="targetsContainer"> <div class="targetsContainer">
<dees-statsgrid .tiles=${statsTiles}></dees-statsgrid> <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
<dees-table <dees-table
@@ -81,8 +82,8 @@ export class OpsViewNetworkTargets extends DeesElement {
name: 'Create Target', name: 'Create Target',
iconName: 'lucide:plus', iconName: 'lucide:plus',
type: ['header' as const], type: ['header' as const],
actionFunc: async (_: any, table: any) => { actionFunc: async () => {
await this.showCreateTargetDialog(table); await this.showCreateTargetDialog();
}, },
}, },
{ {
@@ -96,16 +97,18 @@ export class OpsViewNetworkTargets extends DeesElement {
{ {
name: 'Edit', name: 'Edit',
iconName: 'lucide:pencil', iconName: 'lucide:pencil',
type: ['contextmenu' as const], type: ['inRow', 'contextmenu'] as any,
actionFunc: async (target: interfaces.data.INetworkTarget, table: any) => { actionFunc: async (actionData: any) => {
await this.showEditTargetDialog(target, table); const target = actionData.item as interfaces.data.INetworkTarget;
await this.showEditTargetDialog(target);
}, },
}, },
{ {
name: 'Delete', name: 'Delete',
iconName: 'lucide:trash2', iconName: 'lucide:trash2',
type: ['contextmenu' as const], type: ['inRow', 'contextmenu'] as any,
actionFunc: async (target: interfaces.data.INetworkTarget) => { actionFunc: async (actionData: any) => {
const target = actionData.item as interfaces.data.INetworkTarget;
await this.deleteTarget(target); 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'); const { DeesModal } = await import('@design.estate/dees-catalog');
DeesModal.createAndShow({ DeesModal.createAndShow({
heading: 'Create Network Target', heading: 'Create Network Target',
@@ -128,10 +131,12 @@ export class OpsViewNetworkTargets extends DeesElement {
</dees-form> </dees-form>
`, `,
menuOptions: [ menuOptions: [
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
{ {
name: 'Create', name: 'Create',
action: async (modalArg: any) => { 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 data = await form.collectFormData();
await appstate.profilesTargetsStatePart.dispatchAction(appstate.createTargetAction, { await appstate.profilesTargetsStatePart.dispatchAction(appstate.createTargetAction, {
@@ -143,12 +148,11 @@ export class OpsViewNetworkTargets extends DeesElement {
modalArg.destroy(); 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 hostStr = Array.isArray(target.host) ? target.host.join(', ') : target.host;
const { DeesModal } = await import('@design.estate/dees-catalog'); const { DeesModal } = await import('@design.estate/dees-catalog');
@@ -163,10 +167,12 @@ export class OpsViewNetworkTargets extends DeesElement {
</dees-form> </dees-form>
`, `,
menuOptions: [ menuOptions: [
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
{ {
name: 'Save', name: 'Save',
action: async (modalArg: any) => { 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 data = await form.collectFormData();
await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateTargetAction, { await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateTargetAction, {
@@ -179,7 +185,6 @@ export class OpsViewNetworkTargets extends DeesElement {
modalArg.destroy(); modalArg.destroy();
}, },
}, },
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
], ],
}); });
} }

View File

@@ -64,6 +64,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
]; ];
return html` return html`
<ops-sectionheading>Security Profiles</ops-sectionheading>
<div class="profilesContainer"> <div class="profilesContainer">
<dees-statsgrid .tiles=${statsTiles}></dees-statsgrid> <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
<dees-table <dees-table
@@ -89,8 +90,8 @@ export class OpsViewSecurityProfiles extends DeesElement {
name: 'Create Profile', name: 'Create Profile',
iconName: 'lucide:plus', iconName: 'lucide:plus',
type: ['header' as const], type: ['header' as const],
actionFunc: async (_: any, table: any) => { actionFunc: async () => {
await this.showCreateProfileDialog(table); await this.showCreateProfileDialog();
}, },
}, },
{ {
@@ -104,16 +105,18 @@ export class OpsViewSecurityProfiles extends DeesElement {
{ {
name: 'Edit', name: 'Edit',
iconName: 'lucide:pencil', iconName: 'lucide:pencil',
type: ['contextmenu' as const], type: ['inRow', 'contextmenu'] as any,
actionFunc: async (profile: interfaces.data.ISecurityProfile, table: any) => { actionFunc: async (actionData: any) => {
await this.showEditProfileDialog(profile, table); const profile = actionData.item as interfaces.data.ISecurityProfile;
await this.showEditProfileDialog(profile);
}, },
}, },
{ {
name: 'Delete', name: 'Delete',
iconName: 'lucide:trash2', iconName: 'lucide:trash2',
type: ['contextmenu' as const], type: ['inRow', 'contextmenu'] as any,
actionFunc: async (profile: interfaces.data.ISecurityProfile) => { actionFunc: async (actionData: any) => {
const profile = actionData.item as interfaces.data.ISecurityProfile;
await this.deleteProfile(profile); 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'); const { DeesModal } = await import('@design.estate/dees-catalog');
DeesModal.createAndShow({ DeesModal.createAndShow({
heading: 'Create Security Profile', heading: 'Create Security Profile',
@@ -137,10 +140,12 @@ export class OpsViewSecurityProfiles extends DeesElement {
</dees-form> </dees-form>
`, `,
menuOptions: [ menuOptions: [
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
{ {
name: 'Create', name: 'Create',
action: async (modalArg: any) => { 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 data = await form.collectFormData();
const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : []; const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : [];
const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : []; const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : [];
@@ -158,12 +163,11 @@ export class OpsViewSecurityProfiles extends DeesElement {
modalArg.destroy(); 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'); const { DeesModal } = await import('@design.estate/dees-catalog');
DeesModal.createAndShow({ DeesModal.createAndShow({
heading: `Edit Profile: ${profile.name}`, heading: `Edit Profile: ${profile.name}`,
@@ -177,10 +181,12 @@ export class OpsViewSecurityProfiles extends DeesElement {
</dees-form> </dees-form>
`, `,
menuOptions: [ menuOptions: [
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
{ {
name: 'Save', name: 'Save',
action: async (modalArg: any) => { 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 data = await form.collectFormData();
const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : []; const ipAllowList: string[] = Array.isArray(data.ipAllowList) ? data.ipAllowList : [];
const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : []; const ipBlockList: string[] = Array.isArray(data.ipBlockList) ? data.ipBlockList : [];
@@ -199,7 +205,6 @@ export class OpsViewSecurityProfiles extends DeesElement {
modalArg.destroy(); modalArg.destroy();
}, },
}, },
{ name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
], ],
}); });
} }