fix(ops-ui): improve operations table actions and modal form handling for profiles and network targets
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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...');
|
||||||
|
|||||||
@@ -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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() },
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() },
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user