diff --git a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html
index aa459edda..50247cd14 100644
--- a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html
+++ b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts
index 68d834553..62f82a5cb 100644
--- a/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts
+++ b/ui/src/app/pages/datasets/policies.agent/list/agent.policy.list.component.ts
@@ -85,6 +85,19 @@ export class AgentPolicyListComponent
filters$!: Observable
;
filteredPolicies$: Observable;
+ contextMenuRow: any;
+
+ showContextMenu = false;
+ menuPositionLeft: number;
+ menuPositionTop: number;
+
+ policyContextMenu = [
+ {icon: 'search-outline', action: 'openview'},
+ {icon:'edit-outline', action: 'openview'},
+ {icon: 'copy-outline', action: 'openduplicate'},
+ {icon: 'trash-outline', action: 'opendelete'},
+ ];
+
constructor(
private cdr: ChangeDetectorRef,
private dialogService: NbDialogService,
@@ -152,6 +165,25 @@ export class AgentPolicyListComponent
);
}
+ onTableContextMenu(event) {
+ event.event.preventDefault();
+ event.event.stopPropagation();
+ if (event.type === 'body') {
+ this.contextMenuRow = {
+ objectType: 'policy',
+ ...event.content
+ }
+ this.menuPositionLeft = event.event.clientX;
+ this.menuPositionTop = event.event.clientY;
+ this.showContextMenu = true;
+ }
+ }
+ handleContextClick() {
+ if (this.showContextMenu) {
+ this.showContextMenu = false;
+ }
+ }
+
onOpenDuplicatePolicy(agentPolicy: any) {
const policy = agentPolicy.name;
this.dialogService
diff --git a/ui/src/app/pages/fleet/agents/list/agent.list.component.html b/ui/src/app/pages/fleet/agents/list/agent.list.component.html
index adf5a4a5d..e3821b3f5 100644
--- a/ui/src/app/pages/fleet/agents/list/agent.list.component.html
+++ b/ui/src/app/pages/fleet/agents/list/agent.list.component.html
@@ -1,4 +1,4 @@
-
+
+
;
private currentComponentWidth;
+ contextMenuRow: any;
+
+ showContextMenu = false;
+ menuPositionLeft: number;
+ menuPositionTop: number;
+
+ agentContextMenu = [
+ {icon: 'search-outline', action: 'openview'},
+ {icon:'edit-outline', action: 'openview'},
+ {icon: 'trash-outline', action: 'opendelete'},
+ ];
constructor(
private cdr: ChangeDetectorRef,
private dialogService: NbDialogService,
@@ -191,6 +202,25 @@ export class AgentListComponent implements AfterViewInit, AfterViewChecked, OnDe
}
}
+ onTableContextMenu(event) {
+ event.event.preventDefault();
+ event.event.stopPropagation();
+ if (event.type === 'body') {
+ this.contextMenuRow = {
+ objectType: 'agent',
+ ...event.content
+ }
+ this.menuPositionLeft = event.event.clientX;
+ this.menuPositionTop = event.event.clientY;
+ this.showContextMenu = true;
+ }
+ }
+ handleContextClick() {
+ if (this.showContextMenu) {
+ this.showContextMenu = false;
+ }
+ }
+
ngAfterViewInit() {
this.columns = [
{
diff --git a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html
index 56c817877..9bce885c6 100644
--- a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html
+++ b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts
index 8c1c67e64..a33961385 100644
--- a/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts
+++ b/ui/src/app/pages/fleet/groups/list/agent.group.list.component.ts
@@ -90,6 +90,18 @@ export class AgentGroupListComponent
filters$!: Observable
;
filteredGroups$: Observable;
+ contextMenuRow: any;
+
+ showContextMenu = false;
+ menuPositionLeft: number;
+ menuPositionTop: number;
+
+ agentGroupContextMenu = [
+ {icon: 'search-outline', action: 'openview'},
+ {icon:'edit-outline', action: 'openedit'},
+ {icon: 'trash-outline', action: 'opendelete'},
+ ];
+
constructor(
private cdr: ChangeDetectorRef,
private dialogService: NbDialogService,
@@ -142,6 +154,25 @@ export class AgentGroupListComponent
);
}
+ onTableContextMenu(event) {
+ event.event.preventDefault();
+ event.event.stopPropagation();
+ if (event.type === 'body') {
+ this.contextMenuRow = {
+ objectType: 'group',
+ ...event.content
+ }
+ this.menuPositionLeft = event.event.clientX;
+ this.menuPositionTop = event.event.clientY;
+ this.showContextMenu = true;
+ }
+ }
+ handleContextClick() {
+ if (this.showContextMenu) {
+ this.showContextMenu = false;
+ }
+ }
+
ngOnDestroy(): void {
if (this.groupsSubscription) {
this.groupsSubscription.unsubscribe();
diff --git a/ui/src/app/pages/pages.module.ts b/ui/src/app/pages/pages.module.ts
index eb3d31a38..f65ac54ed 100644
--- a/ui/src/app/pages/pages.module.ts
+++ b/ui/src/app/pages/pages.module.ts
@@ -14,6 +14,7 @@ import {
NbButtonModule,
NbCardModule,
NbCheckboxModule,
+ NbContextMenuModule,
NbDialogService,
NbFormFieldModule,
NbInputModule,
@@ -73,6 +74,7 @@ import { PagesComponent } from './pages.component';
import { SinkViewComponent } from './sinks/view/sink.view.component';
import { DeleteSelectedComponent } from 'app/shared/components/delete/delete.selected.component';
import { PolicyDuplicateComponent } from './datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation';
+import { TableContextMenu } from 'app/shared/components/tableContexMenu/table-context-menu-component';
@NgModule({
imports: [
@@ -108,6 +110,7 @@ import { PolicyDuplicateComponent } from './datasets/policies.agent/duplicate/ag
A11yModule,
NbAutocompleteModule,
SharedModule,
+ NbContextMenuModule,
],
exports: [
SharedModule,
diff --git a/ui/src/app/pages/sinks/list/sink.list.component.html b/ui/src/app/pages/sinks/list/sink.list.component.html
index 2e54cbb44..7084e03db 100644
--- a/ui/src/app/pages/sinks/list/sink.list.component.html
+++ b/ui/src/app/pages/sinks/list/sink.list.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/ui/src/app/pages/sinks/list/sink.list.component.ts b/ui/src/app/pages/sinks/list/sink.list.component.ts
index cb18d5225..ede7a5ab9 100644
--- a/ui/src/app/pages/sinks/list/sink.list.component.ts
+++ b/ui/src/app/pages/sinks/list/sink.list.component.ts
@@ -85,6 +85,18 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes
filters$!: Observable
;
filteredSinks$: Observable;
+ contextMenuRow: any;
+
+ showContextMenu: boolean;
+ menuPositionLeft: number;
+ menuPositionTop: number;
+
+ sinkContextMenu = [
+ {icon: 'search-outline', action: 'openview'},
+ {icon:'edit-outline', action: 'openview'},
+ {icon: 'trash-outline', action: 'opendelete'},
+ ];
+
constructor(
private cdr: ChangeDetectorRef,
private dialogService: NbDialogService,
@@ -140,6 +152,7 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes
this.filters$,
this.filterOptions,
);
+ this.showContextMenu = false;
}
ngOnDestroy(): void {
@@ -234,6 +247,25 @@ export class SinkListComponent implements AfterViewInit, AfterViewChecked, OnDes
];
}
+ onTableContextMenu(event) {
+ event.event.preventDefault();
+ event.event.stopPropagation();
+ if (event.type === 'body') {
+ this.contextMenuRow = {
+ objectType: 'sink',
+ ...event.content
+ }
+ this.menuPositionLeft = event.event.clientX;
+ this.menuPositionTop = event.event.clientY;
+ this.showContextMenu = true;
+ }
+ }
+ handleContextClick() {
+ if (this.showContextMenu) {
+ this.showContextMenu = false;
+ }
+ }
+
onOpenAdd() {
this.router.navigate(['add'], { relativeTo: this.route });
}
diff --git a/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.html b/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.html
new file mode 100644
index 000000000..50d722f62
--- /dev/null
+++ b/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.html
@@ -0,0 +1,6 @@
+
+
+
diff --git a/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.scss b/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.scss
new file mode 100644
index 000000000..9dba836ff
--- /dev/null
+++ b/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.scss
@@ -0,0 +1,28 @@
+.floating-div {
+ position: absolute;
+ transform: translate(3%, -90%);
+ background-color: #171c30;
+ padding: 7px 10px;
+ border-radius: 30px;
+ border-bottom-left-radius: 0;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
+ z-index: 9999;
+}
+.button-action {
+ border: none;
+ background-color: transparent;
+ color: #969fb9;
+ outline: none;
+}
+.openview, .openedit, .openduplicate {
+ transition: color 0.3s ease;
+ &:hover {
+ color: #3089fc;
+ }
+}
+.opendelete {
+ transition: color 0.3s ease;
+ &:hover {
+ color: #df316f;
+ }
+}
diff --git a/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.ts b/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.ts
new file mode 100644
index 000000000..989e076e9
--- /dev/null
+++ b/ui/src/app/shared/components/tableContexMenu/table-context-menu-component.ts
@@ -0,0 +1,181 @@
+import { Component, Input } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { NbDialogService } from '@nebular/theme';
+import { AgentGroupsService } from 'app/common/services/agents/agent.groups.service';
+import { AgentPoliciesService } from 'app/common/services/agents/agent.policies.service';
+import { AgentsService } from 'app/common/services/agents/agents.service';
+import { NotificationsService } from 'app/common/services/notifications/notifications.service';
+import { OrbService } from 'app/common/services/orb.service';
+import { SinksService } from 'app/common/services/sinks/sinks.service';
+import { AgentPolicyDeleteComponent } from 'app/pages/datasets/policies.agent/delete/agent.policy.delete.component';
+import { PolicyDuplicateComponent } from 'app/pages/datasets/policies.agent/duplicate/agent.policy.duplicate.confirmation';
+import { AgentDeleteComponent } from 'app/pages/fleet/agents/delete/agent.delete.component';
+import { AgentGroupDeleteComponent } from 'app/pages/fleet/groups/delete/agent.group.delete.component';
+import { AgentGroupDetailsComponent } from 'app/pages/fleet/groups/details/agent.group.details.component';
+import { SinkDeleteComponent } from 'app/pages/sinks/delete/sink.delete.component';
+
+@Component({
+ selector: 'ngx-table-context-menu',
+ templateUrl: './table-context-menu-component.html',
+ styleUrls: ['./table-context-menu-component.scss']
+})
+export class TableContextMenu {
+
+ @Input()
+ items: any[];
+
+ @Input()
+ top: number;
+
+ @Input()
+ left: number;
+
+ @Input()
+ rowObject: any;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private dialogService: NbDialogService,
+ private orb: OrbService,
+ protected agentService: AgentsService,
+ protected notificationsService: NotificationsService,
+ private agentGroupsService: AgentGroupsService,
+ private sinkService: SinksService,
+ private agentPoliciesService: AgentPoliciesService,
+ ) {
+
+ }
+ handleClick(item: any) {
+ const { action } = item;
+ if (action === 'openview') {
+ this.openView();
+ } else if (action === 'opendelete') {
+ this.openDelete();
+ } else if (action === 'openduplicate') {
+ this.openDuplicate();
+ } else if (action === 'openedit') {
+ this.openGroupEdit();
+ }
+ }
+
+ openGroupEdit() {
+ this.router.navigate([`edit/${this.rowObject.id}`], {
+ state: { agentGroup: this.rowObject, edit: true },
+ relativeTo: this.route,
+ });
+ }
+
+ openView() {
+ const { objectType, id } = this.rowObject;
+ if (objectType === 'group') {
+ this.dialogService
+ .open(AgentGroupDetailsComponent, {
+ context: { agentGroup: this.rowObject },
+ autoFocus: true,
+ closeOnEsc: true,
+ })
+ .onClose.subscribe((resp) => {
+ if (resp) {
+ this.openGroupEdit();
+ }
+ });
+ }
+ else {
+ this.router.navigate([`view/${id}`], {
+ relativeTo: this.route,
+ });
+ }
+ }
+ openDelete() {
+ const { objectType, name, id } = this.rowObject;
+
+ const deleteCallback = () => {
+ this.notificationsService.success(
+ `${objectType.charAt(0).toUpperCase() + objectType.slice(1)} successfully deleted`,
+ ''
+ );
+ this.orb.refreshNow();
+ };
+
+ if (objectType === 'agent') {
+ this.dialogService
+ .open(AgentDeleteComponent, {
+ context: { name },
+ autoFocus: true,
+ closeOnEsc: true,
+ })
+ .onClose.subscribe((confirm) => {
+ if (confirm) {
+ this.agentService.deleteAgent(id).subscribe(deleteCallback);
+ }
+ });
+ } else if (objectType === 'group') {
+ this.dialogService
+ .open(AgentGroupDeleteComponent, {
+ context: { name },
+ autoFocus: true,
+ closeOnEsc: true,
+ })
+ .onClose.subscribe((confirm) => {
+ if (confirm) {
+ this.agentGroupsService.deleteAgentGroup(id).subscribe(deleteCallback);
+ }
+ });
+ } else if (objectType === 'policy') {
+ this.dialogService
+ .open(AgentPolicyDeleteComponent, {
+ context: { name },
+ autoFocus: true,
+ closeOnEsc: true,
+ })
+ .onClose.subscribe((confirm) => {
+ if (confirm) {
+ this.agentPoliciesService.deleteAgentPolicy(id).subscribe(deleteCallback);
+ }
+ });
+ } else if (objectType === 'sink') {
+ this.dialogService
+ .open(SinkDeleteComponent, {
+ context: { sink: this.rowObject },
+ autoFocus: true,
+ closeOnEsc: true,
+ })
+ .onClose.subscribe((confirm) => {
+ if (confirm) {
+ this.sinkService.deleteSink(id).subscribe(deleteCallback);
+ }
+ });
+ }
+ }
+
+ openDuplicate() {
+ const policy = this.rowObject.name;
+ this.dialogService
+ .open(PolicyDuplicateComponent, {
+ context: { policy },
+ autoFocus: true,
+ closeOnEsc: true,
+ })
+ .onClose.subscribe((confirm) => {
+ if (confirm) {
+ this.duplicatePolicy(this.rowObject);
+ }
+ });
+ }
+ duplicatePolicy(agentPolicy: any) {
+ this.agentPoliciesService
+ .duplicateAgentPolicy(agentPolicy.id)
+ .subscribe((newAgentPolicy) => {
+ if (newAgentPolicy?.id) {
+ this.notificationsService.success(
+ 'Agent Policy Duplicated',
+ `New Agent Policy Name: ${newAgentPolicy?.name}`,
+ );
+ this.router.navigate([`view/${newAgentPolicy.id}`], {
+ relativeTo: this.route,
+ });
+ }
+ });
+ }
+}
diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts
index 28621b2c4..618cc5498 100644
--- a/ui/src/app/shared/shared.module.ts
+++ b/ui/src/app/shared/shared.module.ts
@@ -66,6 +66,7 @@ import {EmptyInputDirective} from 'app/shared/directives/empty-input.directive';
import { AgentBackendsComponent } from './components/orb/agent/agent-backends/agent-backends.component';
import { SinkDetailsComponent } from './components/orb/sink/sink-details/sink-details.component';
import { SinkConfigComponent } from './components/orb/sink/sink-config/sink-config.component';
+import { TableContextMenu } from './components/tableContexMenu/table-context-menu-component';
@NgModule({
imports: [
@@ -136,6 +137,7 @@ import { SinkConfigComponent } from './components/orb/sink/sink-config/sink-conf
EmptyInputDirective,
SinkDetailsComponent,
SinkConfigComponent,
+ TableContextMenu,
],
exports: [
ThemeModule,
@@ -161,6 +163,7 @@ import { SinkConfigComponent } from './components/orb/sink/sink-config/sink-conf
PolicyDetailsComponent,
PolicyInterfaceComponent,
PolicyDatasetsComponent,
+ TableContextMenu,
GroupedAgentsComponent,
PolicyGroupsComponent,
PrettyYamlPipe,