Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.routes.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.routes.ts	(revision 2679)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.routes.ts	(working copy)
@@ -20,6 +20,10 @@
 import {VpnManagement} from './components/vpn-management/vpn-management';
 import {UpgradeCentre} from './components/upgrade-centre/upgrade-centre';
 import {VpnDetailsOverview} from './components/sub-components/vpn-details-overview/vpn-details-overview';
+import {VpnAclGroups} from './components/sub-components/vpn-acl-groups/vpn-acl-groups';
+import {
+  VpnResourceGroupOverview
+} from './components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview';
 
 
 export const routes: Routes = [
@@ -113,14 +117,28 @@
             data: {roles: ['super_admin', 'device_admin', 'common_admin']}
           },
           {
+            path: 'details/acl/:deviceName/:serviceName/:groupName',
+            component: VpnAclGroups,
+            data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+          },
+          {
+            path: 'details/vpn-resource/:deviceName/:serviceName/:groupName',
+            component: VpnResourceGroupOverview,
+            data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+          },
+          {
             path: 'details/:deviceName/:serviceName',
             component: VpnDetailsOverview,
             data: {roles: ['super_admin', 'device_admin', 'common_admin']}
-          }
+          },
         ]
       },
-      {path: 'upgrade-centre', component: UpgradeCentre, data: {roles: ['super_admin', 'device_admin', 'common_admin']}},
       {
+        path: 'upgrade-centre',
+        component: UpgradeCentre,
+        data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+      },
+      {
         path: 'configuration-hub',
         data: {roles: ['super_admin', 'device_admin', 'common_admin'],},
         children: [
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/app-notification/app-notification.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/app-notification/app-notification.scss	(revision 2679)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/app-notification/app-notification.scss	(working copy)
@@ -22,6 +22,7 @@
 .message {
   flex: 1;
   margin-right: 10px;
+  white-space: pre-wrap;
 }
 .dismiss-btn {
   background: none;
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.html	(working copy)
@@ -0,0 +1,28 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>
+      <a class="back-to-main-page" (click)="backToACLGroups()">
+        <fa-icon [icon]="['far', 'circle-left']"></fa-icon>
+        Virtual Site - {{ serviceName }} / ACL Group - {{groupName}}
+      </a>
+    </mat-card-title>
+  </mat-card-header>
+</mat-card>
+<div class="tab-container">
+  <mat-tab-group animationDuration="0ms" [selectedIndex]="selectedTabIndex" (selectedTabChange)="onTabChange($event)">
+    <mat-tab label="ACL Resource">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-acl-resources/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="Rule">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-acl-rules/>
+        </div>
+      </ng-template>
+    </mat-tab>
+  </mat-tab-group>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnAclGroups } from './vpn-acl-groups';
+
+describe('VpnAclGroups', () => {
+  let component: VpnAclGroups;
+  let fixture: ComponentFixture<VpnAclGroups>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnAclGroups]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnAclGroups);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-groups/vpn-acl-groups.ts	(working copy)
@@ -0,0 +1,81 @@
+import {Component, OnInit} from '@angular/core';
+import {FaIconComponent} from '@fortawesome/angular-fontawesome';
+import {MatCard, MatCardHeader, MatCardTitle} from '@angular/material/card';
+import {MatTab, MatTabChangeEvent, MatTabContent, MatTabGroup} from '@angular/material/tabs';
+import {VpnAclResourceGroups} from '../vpn-acl-resource-groups/vpn-acl-resource-groups';
+import {VpnHardwareId} from '../vpn-hardware-id/vpn-hardware-id';
+import {VpnResourceGroups} from '../vpn-resource-groups/vpn-resource-groups';
+import {SharedModule} from '../../../shared/shared-module';
+import {ActivatedRoute, Router} from '@angular/router';
+import {VpnAclResources} from '../vpn-acl-resources/vpn-acl-resources';
+import {VpnAclRules} from '../vpn-acl-rules/vpn-acl-rules';
+
+@Component({
+  selector: 'app-vpn-acl-groups',
+  imports: [
+    SharedModule,
+    VpnAclResources,
+    VpnAclRules,
+  ],
+  templateUrl: './vpn-acl-groups.html',
+  styleUrl: './vpn-acl-groups.scss'
+})
+export class VpnAclGroups implements OnInit {
+  deviceName: string | null = '';
+  serviceName: string | null = '';
+  groupName: string | null = '';
+  serviceDetails: any = null;
+
+  selectedTabIndex: number = 0;
+  private tabNames: string[] = [
+    'ACL Resource',
+    'Rule',
+  ];
+
+  constructor(
+    private _route: ActivatedRoute,
+    private _router: Router,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName');
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName');
+    this.groupName = this._route.snapshot.paramMap.get('groupName');
+    this.serviceDetails = history.state.serviceDetails;
+
+    this._route.queryParams.subscribe(params => {
+      const tabParam = params['tab'];
+      if (tabParam) {
+        const index = this.tabNames.indexOf(tabParam);
+        if (index !== -1) {
+          this.selectedTabIndex = index;
+        } else {
+          this.selectedTabIndex = 0;
+        }
+      } else {
+        this.updateQueryParams(this.selectedTabIndex);
+      }
+    });
+  }
+
+  onTabChange(event: MatTabChangeEvent): void {
+    this.selectedTabIndex = event.index;
+    this.updateQueryParams(event.index);
+  }
+
+  backToACLGroups(): void {
+    this._router.navigate(['/vpn-management/details', this.deviceName, this.serviceName]);
+  }
+
+  private updateQueryParams(tabIndex: number): void {
+    const tabName = this.tabNames[tabIndex];
+    if (tabName) {
+      this._router.navigate([], {
+        relativeTo: this._route,
+        queryParams: {tab: tabName},
+        queryParamsHandling: 'merge'
+      });
+    }
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/create-acl-resource-group.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/create-acl-resource-group.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/create-acl-resource-group.html	(working copy)
@@ -0,0 +1,98 @@
+<h2 mat-dialog-title>Add ACL Resource Group</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="name" class="form-label">Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="name"
+          formControlName="name"
+          matInput
+          placeholder="Name"
+          type="text"
+        />
+        @if (configForm.get('name')?.invalid && configForm.get('name')?.touched) {
+          <mat-error>
+            @if (configForm.get('name')?.errors?.['required']) {
+              Name is required.
+            } @else if (configForm.get('name')?.errors) {
+              Invalid name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="type" class="form-label">Type *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="type">
+          @for (_type of typeOptions; track _type) {
+            <mat-option [value]="_type?.value">{{ _type?.label }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('type')?.invalid && configForm.get('type')?.touched) {
+          <mat-error>
+            @if (configForm.get('type')?.errors?.['required']) {
+              Type is required.
+            } @else {
+              Invalid type format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="description" class="form-label">Description</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="description"
+          formControlName="description"
+          matInput
+          placeholder="Description"
+          type="text"
+        />
+        @if (configForm.get('description')?.invalid && configForm.get('description')?.touched) {
+          <mat-error>
+            @if (configForm.get('description')?.errors?.['required']) {
+              Description is required.
+            } @else if (configForm.get('description')?.errors) {
+              Invalid description format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="resource_list" class="form-label">Resource List</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <textarea matInput formControlName="resource_list" rows="7" cols="40"></textarea>
+        <mat-hint>Enter one resource per line.</mat-hint>
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="resource_list" class="form-label">Resource Examples (click to add):</label>
+      <div class="a-link">
+        @for (_example of examples[configForm.value.type]; track _example) {
+          <a (click)="addExampleToResourceList(_example)">{{_example}}</a><br>
+        }
+      </div>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/sync-acl-groups.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/sync-acl-groups.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/sync-acl-groups.html	(working copy)
@@ -0,0 +1,41 @@
+<h2 mat-dialog-title>Sync ACL Groups</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="services" class="form-label">Virtual site *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="services" multiple>
+          @for (_vsite of serviceOptions; track _vsite) {
+            <mat-option [value]="_vsite">{{ _vsite?.name }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('services')?.invalid && configForm.get('services')?.touched) {
+          <mat-error>
+            @if (configForm.get('services')?.errors?.['required']) {
+              Virtual site is required.
+            } @else {
+              Invalid virtual site format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.html	(working copy)
@@ -0,0 +1,64 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addACLResourceGroup()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ getGlobalSerial(i) }}</td>
+    </ng-container>
+    <ng-container matColumnDef="groupName">
+      <th mat-header-cell *matHeaderCellDef> Group Name</th>
+      <td mat-cell *matCellDef="let element">
+        <a class="details-page-link" (click)="goToDetails(element)">{{ element?.name }}</a>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="type">
+      <th mat-header-cell *matHeaderCellDef> Type</th>
+      <td mat-cell *matCellDef="let element">{{ element?.type | titlecase }}</td>
+    </ng-container>
+    <ng-container matColumnDef="description">
+      <th mat-header-cell *matHeaderCellDef> Device Group</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.desc }}</td>
+    </ng-container>
+    <ng-container matColumnDef="editable">
+      <th mat-header-cell *matHeaderCellDef> Editable</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          @if (!element.editable) {
+            <fa-icon [icon]="['far', 'xmark-circle']" class="delete-icon"
+                     matTooltip="Deny"></fa-icon>&nbsp;&nbsp;
+          } @else {
+            <fa-icon [icon]="['far', 'check-circle']" class="success-icon"
+                     matTooltip="Approve"></fa-icon>&nbsp;&nbsp;
+          }
+        </div>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['fas', 'gears']" size="lg" matTooltip="Sync"
+                   (click)="syncGroup(element)"></fa-icon> &nbsp;&nbsp;
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteGroup(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnAclResourceGroups } from './vpn-acl-resource-groups';
+
+describe('VpnAclResourceGroups', () => {
+  let component: VpnAclResourceGroups;
+  let fixture: ComponentFixture<VpnAclResourceGroups>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnAclResourceGroups]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnAclResourceGroups);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resource-groups/vpn-acl-resource-groups.ts	(working copy)
@@ -0,0 +1,769 @@
+import {ChangeDetectorRef, Component, inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../../shared/shared-module';
+import {VpnService} from '../../../services/vpn-service';
+import {ActivatedRoute, Router} from '@angular/router';
+import {take} from 'rxjs/operators';
+import {NotificationService} from '../../../services/notification';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {Subscription} from 'rxjs';
+import {Confirmation} from '../../../services/confirmation';
+import {DeviceService} from '../../../services/device-service';
+
+@Component({
+  selector: 'app-vpn-acl-resource-groups',
+  imports: [SharedModule],
+  templateUrl: './vpn-acl-resource-groups.html',
+  styleUrl: './vpn-acl-resource-groups.scss'
+})
+export class VpnAclResourceGroups implements OnInit {
+
+  deviceName: string = '';
+  serviceName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'groupName', 'type', 'description', 'editable', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  constructor(
+    private _vpn: VpnService,
+    private _route: ActivatedRoute,
+    private _cdRef: ChangeDetectorRef,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation,
+    private _router: Router,
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+  }
+
+  ngOnInit() {
+    setTimeout(() => {
+      this.dataSource.paginator = this.paginator;
+      this.getACLResourceGroups();
+    })
+  }
+
+  getACLResourceGroups() {
+    let payload: any = {
+      cmd: "show acl resourcegroup",
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          result?.contents.forEach((content: any) => {
+            if (content?.cmdid === 'acl resourcegroup network') {
+              this.dataSource.data.push({
+                "name": content['res_grp_name'],
+                "type": 'network',
+                'desc': content['desc'],
+                'editable': true
+              })
+            }
+            if (content?.cmdid === '##acl resourcegroup network') {
+              this.dataSource.data.push({
+                "name": content['res_grp_name'],
+                "type": 'network',
+                'desc': content['desc'],
+                'editable': false
+              })
+            }
+            if (content?.cmdid === 'acl resourcegroup web') {
+              this.dataSource.data.push({
+                "name": content['res_grp_name'],
+                "type": 'web',
+                'desc': content['desc'],
+                'editable': true
+              })
+            }
+            if (content?.cmdid === '##acl resourcegroup web') {
+              this.dataSource.data.push({
+                "name": content['res_grp_name'],
+                "type": 'web',
+                'desc': content['desc'],
+                'editable': false
+              })
+            }
+            if (content?.cmdid === 'acl resourcegroup fileshare') {
+              this.dataSource.data.push({
+                "name": content['res_grp_name'],
+                "type": 'fileshare',
+                'desc': content['desc'],
+                'editable': true
+              })
+            }
+            if (content?.cmdid === '##acl resourcegroup fileshare') {
+              this.dataSource.data.push({
+                "name": content['res_grp_name'],
+                "type": 'fileshare',
+                'desc': content['desc'],
+                'editable': false
+              })
+            }
+          })
+          this.dataSource.paginator = this.paginator;
+          this.totalRecords = this.dataSource.data.length;
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    })
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+  addACLResourceGroup() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateACLResourceGroupDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getACLResourceGroups();
+      }
+    })
+  }
+
+  goToDetails(_group: any) {
+    this._router.navigate(['/vpn-management/details/acl', this.deviceName, this.serviceName, _group?.name], {
+      state: {}
+    });
+  }
+
+  deleteGroup(_group: any) {
+    let confirmMsg = `Are you sure you want to delete the rule - ${_group?.name}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_group?.name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        const postData: any = {'cmd': 'no acl resourcegroup "' + _group.name + '"', 'vsite_name': this.serviceName}
+        this._vpn.executeAGCLICommand(this.deviceName, postData)
+          .pipe(take(1)).subscribe({
+          next: (res: any) => {
+            if (res && res.contents === "") {
+              this._notification.showSuccess('The ACL Resource Group has been deleted successfully.');
+              this.getACLResourceGroups();
+            }
+          },
+          error: error => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    })
+  }
+
+  syncGroup(_group: any) {
+    console.log('syncGroup', _group);
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      base_acl: _group
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(SyncACLResourceGroupDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getACLResourceGroups();
+      }
+    })
+  }
+}
+
+@Component({
+  selector: 'create-acl-resource-group',
+  templateUrl: './create-acl-resource-group.html',
+  imports: [SharedModule]
+})
+export class CreateACLResourceGroupDialog implements OnInit, OnDestroy {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateACLResourceGroupDialog>);
+
+  configForm!: FormGroup;
+
+  typeOptions: any = [
+    {value: 'network', label: 'Network'},
+    {value: 'web', label: 'Web'},
+    {value: 'fileshare', label: 'Fileshare'},
+  ];
+
+  examples: any = {
+    'network': [
+      'udp://10.1.1.1:25',
+      '17://10.1.1.2:25',
+      'tcp://10.1.1.0/24:25,1080,2200',
+      'udp://10.10.10.0/24:1-65535',
+      'icmp://10.10.10.10/255.255.255.255',
+      '10.10.10.0/24',
+      '0.0.0.0/0',
+      '[::]/0',
+      '[abcd::]/64',
+      '[1022:abcd:1111:2222:3333:4444::1234]/128',
+    ],
+    'web': [
+      'http://*.domain.com/public/*',
+      'https://www.domain.com:443/*',
+      '10.10.10.0/255.255.255.0:80-443/public/*',
+      '10.10.10.1/32:*/public/*'
+    ],
+    'fileshare': [
+      '\\\\10.10.10.1\directory',
+      '\\\\10.10.10.0/255.255.255.0\*',
+      '\\\\10.10.10.0/24\directory',
+    ],
+  }
+
+  private categorySubscription!: Subscription | undefined;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.configForm = this._formBuilder.group({
+      name: ['', Validators.required],
+      type: ['network', Validators.required],
+      description: [''],
+      resource_list: [''],
+    })
+  }
+
+  addExampleToResourceList(_example: any): void {
+    let resourceList: any = this.configForm.value.resource_list;
+    if (resourceList.length > 0) {
+      resourceList += '\n' + _example
+    } else {
+      resourceList = _example
+    }
+    this.configForm.patchValue({
+      resource_list: resourceList,
+    })
+  }
+
+  onSubmit(): void {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      'cmd': 'acl resourcegroup ' + this.configForm.value.type + ' "' + this.configForm.value.name + '" "' + this.configForm.value.description + '"',
+      'vsite_name': this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (res: any) => {
+        if (res && res.contents == "") {
+          this.updateResourceList(res);
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  updateResourceList(_response: any) {
+    let acl_list: any = this.configForm.value.resource_list.split("\n");
+    if (acl_list && acl_list.length > 0 && !(acl_list.length === 1 && acl_list[0] === '')) {
+      let cmd_list: any = ["sw \"" + this.data?.serviceName + "\""];
+      let cmd_dict: any = {};
+      acl_list.forEach((item: any) => {
+        if (item) {
+          cmd_list.push('acl resource "' + this.configForm.value.name + '" "' + item + '"');
+          cmd_dict['acl resource "' + this.configForm.value.name + '" "' + item + '"'] = item;
+        }
+      })
+      let payload: any = {'config': cmd_list.join('\n')};
+      this._vpn.batchAGCLICommand(this.data?.deviceName, payload)
+        .pipe(take(1)).subscribe({
+        next: (res: any) => {
+          if (res && res.contents !== "Successful") {
+            let has_error = false;
+            let res_list: any = res.data['contents'].split('\n');
+            let res_dict: any = {};
+            res_list.forEach((res_str: any) => {
+              if (!res_str) {
+                return;
+              }
+              if (res_str.indexOf('The same acl resource has been configured already!') != -1) {
+                return;
+              }
+              has_error = true
+              let tmp_list = res_str.split(':"');
+              if (tmp_list[1].indexOf("^") != -1) {
+                res_dict[tmp_list[0]] = 'Parameter error';
+              } else {
+                let tmp_str = tmp_list[1].replace(/^\"|\"$/g, '');
+                tmp_str = tmp_str.replace(/\\/g, '');
+                res_dict[tmp_list[0]] = tmp_str;
+              }
+            })
+            if (has_error) {
+              let result_list: any = []
+              result_list.push({
+                "type": "success",
+                "message": 'Add ACL resource group successfully'
+              })
+              cmd_list.forEach((cmd_str: any) => {
+                if (!cmd_str || cmd_str == "sw \"" + this.data?.serviceName + "\"") {
+                  return;
+                }
+                if (res_dict[cmd_str]) {
+                  result_list.push({
+                    "type": "error",
+                    "message": `Add acl resource "${[cmd_dict[cmd_str]]}" failed: ${res_dict[cmd_str]}`
+                  })
+                } else {
+                  result_list.push({
+                    "type": "success",
+                    "message": `Add acl resource "${[cmd_dict[cmd_str]]}" successfully.`
+                  })
+                }
+              })
+              result_list.forEach((item: any) => {
+                if (item.type === "success") {
+                  this._notification.showSuccess(item?.message);
+                } else {
+                  this._notification.showError(item?.message);
+                }
+              })
+            }
+          } else {
+            this._notification.showSuccess('The ACL Resource Group has been created successfully.');
+            this.dialogRef.close(true);
+          }
+        },
+        error: error => {
+          this._notification.showError(`Error: ${error?.message}`);
+        }
+      })
+    } else {
+      this._notification.showSuccess('The ACL Resource Group has been created successfully.');
+      this.dialogRef.close(true);
+    }
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+
+  ngOnDestroy(): void {
+    if (this.categorySubscription) {
+      this.categorySubscription.unsubscribe();
+    }
+  }
+}
+
+@Component({
+  selector: 'sync-acl-groups',
+  templateUrl: './sync-acl-groups.html',
+  imports: [SharedModule]
+})
+export class SyncACLResourceGroupDialog implements OnInit, OnDestroy {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<SyncACLResourceGroupDialog>);
+
+  configForm!: FormGroup;
+  serviceOptions: any = [];
+
+  constructor(
+    private _vpn: VpnService,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _formBuilder: FormBuilder,
+    private _cdRef: ChangeDetectorRef,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._formBuilder.group({
+      services: ['', Validators.required],
+    })
+    setTimeout(() => {
+      this.getAMPDevices();
+    })
+  }
+
+  getAMPDevices() {
+    this.serviceOptions = [];
+    this._device.getAMPDevicesList()
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.length > 0) {
+          result.forEach((_device: any) => {
+            if (_device.type.toLowerCase() === 'ag' || _device.type.toLowerCase() === 'vxag') {
+              this.getVpnServices(_device?.name, _device?.device_group);
+            }
+          })
+        }
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  getVpnServices(aGName: string, device_group: string): void {
+    let payload = {cmd: 'show virtual site config'}
+    this._vpn.getVPNServices(aGName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          let vsites = this.processVirtualSiteData(result?.contents);
+          vsites.forEach((vsite: any) => {
+            vsite.device_name = aGName;
+            vsite.device_group = device_group;
+            if (vsite.deviceName !== this.data?.deviceName && vsite?.name !== this.data?.serviceName) {
+              this.serviceOptions.push(vsite);
+            }
+          })
+        }
+        this._cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  processVirtualSiteData(confList: any[]) {
+    const vsiteDict: { [key: string]: any } = {};
+    const result: any[] = [];
+
+    confList.forEach((data: any) => {
+      const vsiteName = data.vsite_name;
+
+      if (data.cmdid === 'virtual site name') {
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].type = data.vsite_type || '';
+          vsiteDict[vsiteName].vsite_desc = data.vsite_desc || '';
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: data.vsite_type || '',
+            vsite_desc: data.vsite_desc || '',
+            ip: [],
+            ip_str: '',
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site ip') {
+        const ip = data.ip ? data.ip.slice(3, -1) : '';
+        const port = data.port || '';
+        const ipStr = `${ip}:${port}`;
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].ip.push(ipStr);
+          vsiteDict[vsiteName].ip_str += `${ipStr}, `;
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: '',
+            vsite_desc: '',
+            ip: [ipStr],
+            ip_str: `${ipStr}, `,
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site domain') {
+        const domainName = data.domain_name || '';
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].domain.push(domainName);
+          vsiteDict[vsiteName].domain_str += `${domainName}, `;
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: '',
+            vsite_desc: '',
+            ip: [],
+            ip_str: '',
+            domain: [domainName],
+            domain_str: `${domainName}, `
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      }
+    });
+    result.forEach(vsite => {
+      if (vsite.ip_str.endsWith(', ')) {
+        vsite.ip_str = vsite.ip_str.slice(0, -2);
+      }
+      if (vsite.domain_str.endsWith(', ')) {
+        vsite.domain_str = vsite.domain_str.slice(0, -2);
+      }
+    });
+
+    return result;
+  }
+
+  ngOnDestroy() {
+  }
+
+  onSubmit(): void {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let data: any = {
+      device_name: this.data?.deviceName,
+      vsite_name: this.data?.serviceName,
+      base_acl: this.data?.base_acl,
+      vsite: this.configForm.value?.services
+    };
+    let services: any = this.configForm.value?.services;
+    let base_device: any = data?.device_name;
+    let base_vsite: any = data?.vsite_name;
+    let base_acl: any = data?.base_acl?.name;
+    let all_result_list: any = [];
+    let all_has_error: boolean = false;
+    let result: any = [
+      'clear acl resource "' + base_acl + '"',
+      'no acl resourcegroup "' + base_acl + '"',
+      'acl resourcegroup ' + data.base_acl.type + ' "' + base_acl + '" "' + data.base_acl.desc + '"'
+    ];
+    console.log(result, data);
+    let all_dict: any = {}
+    let post_data: any = {'cmd': 'show acl resource "' + base_acl + '"', 'vsite_name': base_vsite};
+    this._vpn.executeAGCLICommand(this.data?.deviceName, post_data)
+      .pipe(take(1)).subscribe({
+      next: (res: any) => {
+        if (res) {
+          console.log(res);
+          let conf_list: any = res?.contents;
+          conf_list.forEach((item: any) => {
+            if (item?.cmdid == 'acl resource') {
+              result.push('acl resource "' + base_acl + '" "' + item?.resource + '"');
+              all_dict['acl resource "' + base_acl + '" "' + item?.resource + '"'] = item?.resource;
+              return;
+            }
+          })
+          let rule_post_data: any = {'cmd': 'show acl rule "" "' + base_acl + '"', 'vsite_name': base_vsite};
+          this._vpn.executeAGCLICommand(this.data?.deviceName, rule_post_data)
+            .pipe(take(1)).subscribe({
+            next: (resp: any) => {
+              conf_list = resp?.contents;
+              let display_name: any = {'R': 'Role', 'U': 'User', 'G': 'Group'};
+              conf_list.forEach((item: any) => {
+                if (item?.cmdid == 'acl rule') {
+                  result.push('acl rule "' + item?.target_name + '" "' + base_acl + '" "' + item?.action + '" ' + item?.priority + ' "' + item?.target_type + '"');
+                  all_dict['acl rule "' + item?.target_name + '" "' + base_acl + '" "' + item?.action + '" ' + item?.priority + ' "' + item?.target_type + '"'] = item?.target_name + '(' + display_name[item?.target_type] + ')';
+                  return;
+                }
+              })
+              let cmd_list: any = [];
+              let cmd_dict: any = {};
+              services.forEach((item: any) => {
+                let cmd_str: any = 'change to "' + item?.name + '"\nsw "' + item?.name + '"\n' + result.join('\n');
+                if (cmd_dict[item?.device_name]) {
+                  cmd_dict[item?.device_name]['cmd_str'] += '\n' + cmd_str;
+                  cmd_dict[item?.device_name]['vsite_list'].push(item?.name);
+                } else {
+                  cmd_dict[item?.device_name] = {
+                    'cmd_str': cmd_str,
+                    'device_name': item?.device_name,
+                    'vsite_list': [item?.name]
+                  }
+                  cmd_list.push(cmd_dict[item?.device_name]);
+                }
+              })
+              let process_done: any = [];
+              cmd_list.forEach((cmd_obj: any) => {
+                post_data = {'config': cmd_obj?.cmd_str};
+                this._vpn.batchAGCLICommand(cmd_obj?.device_name, post_data)
+                  .pipe(take(1)).subscribe({
+                  next: (response: any) => {
+                    if (response?.contents != 'Successful') {
+                      let has_error: boolean = false;
+                      let res_list: any = response?.contents.split('\n');
+                      let result_list: any = [];
+                      let res_dict: any = {};
+                      let curr_vs: any = "";
+                      res_list.forEach((res_str: any) => {
+                        if (!res_str) {
+                          return;
+                        }
+                        if (res_str.indexOf("clear acl") != -1) {
+                          return;
+                        }
+                        if (res_str.indexOf("no acl") != -1) {
+                          return;
+                        }
+                        let tmp_cmd = res_str.split(':"');
+                        if (res_str.indexOf("change to") != -1) {
+                          curr_vs = tmp_cmd[0].replace(/^change to \"|\"$/g, '');
+                          if (!(curr_vs in res_dict)) {
+                            res_dict[curr_vs] = {};
+                          }
+                          return;
+                        }
+                        if (curr_vs) {
+                          let tmp_str = tmp_cmd[1].replace(/^\"|\"$/g, '');
+                          tmp_str = tmp_str.replace(/\\/g, '');
+                          res_dict[curr_vs][tmp_cmd[0]] = tmp_str;
+                          has_error = true;
+                          all_has_error = true;
+                        }
+                      })
+                      if (!has_error) {
+                        all_result_list.push({
+                          type: "success",
+                          message: `Sync ACL resource group to device "${cmd_obj['device_name']}" successfully`
+                        });
+                      } else {
+                        cmd_obj?.vsite_list.forEach((vs: any) => {
+                          if (res_dict?.vs === null || typeof res_dict.vs === 'undefined') {
+                            all_result_list.push({
+                              type: "success",
+                              message: `Sync ACL resource group to device "${cmd_obj['device_name']}" virtual site "${vs}" successfully.`
+                            });
+                          } else {
+                            all_result_list.push({
+                              type: "normal",
+                              message: `Do Syncing on device "${cmd_obj['device_name']}" virtual site "${vs}"`
+                            });
+                          }
+                          result.forEach((cmd: any) => {
+                            if (cmd.indexOf("clear acl") != -1) {
+                              // return;
+                            }
+                            if (cmd.indexOf("no acl") != -1) {
+                              // return;
+                            }
+                            if (cmd.indexOf("acl resourcegroup") != -1) {
+                              if (res_dict[vs][cmd]) {
+                                all_result_list.push({
+                                  type: "error",
+                                  message: `Create ACL resource group failed : ${res_dict[vs][cmd]}`
+                                });
+                              } else {
+                                all_result_list.push({
+                                  type: "success",
+                                  message: `Create ACL resource group successfully`
+                                });
+                              }
+                              // return;
+                            }
+                            if (cmd.indexOf("acl resource ") != -1) {
+                              if (res_dict[vs][cmd]) {
+                                all_result_list.push({
+                                  type: "error",
+                                  message: `Create ACL resource "${all_dict[cmd]}" failed: ${res_dict[vs][cmd]}`
+                                });
+                              } else {
+                                all_result_list.push({
+                                  type: "success",
+                                  message: `Create ACL resource "${all_dict[cmd]}" successfully`
+                                });
+                              }
+                              // return;
+                            }
+                            if (cmd.indexOf("acl rule ") != -1) {
+                              if (res_dict[vs][cmd]) {
+                                all_result_list.push({
+                                  type: "error",
+                                  message: `Create ACL rule "${all_dict[cmd]}" failed: ${res_dict[vs][cmd]}`
+                                });
+                              } else {
+                                all_result_list.push({
+                                  type: "success",
+                                  message: `Create ACL rule "${all_dict[cmd]}" successfully`
+                                });
+                              }
+                              // return;
+                            }
+                          })
+                        })
+                      }
+                      let _success: any = [];
+                      let _failure: any = [];
+                      all_result_list.forEach((msg: any) => {
+                        if (msg?.type === 'normal' || msg?.type === 'success') {
+                          _success.push(msg?.message);
+                        } else {
+                          _failure.push(msg?.message);
+                        }
+                      })
+                      if (_success.length > 0) {
+                        this._notification.showSuccess(_success);
+                      }
+                      if (_failure.length > 0) {
+                        console.log(_failure);
+                        this._notification.showError(_failure);
+                      }
+                      this.dialogRef.close(true);
+                    } else {
+                      this._notification.showSuccess(`The ACL Group resources has been synced successfully.`);
+                    }
+                  },
+                  error: error => {
+                    this._notification.showError(`Error: ${error?.message}`);
+                  }
+                });
+              })
+            },
+            error: error => {
+              this._notification.showError(`Error: ${error?.message}`);
+            }
+          })
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+
+  private translate(key: string, params?: any[]): string {
+    let translatedString = key;
+    if (params) {
+      params.forEach((param, index) => {
+        translatedString = translatedString.replace(`{${index}}`, param);
+      });
+    }
+    return translatedString;
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/create-acl-resource.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/create-acl-resource.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/create-acl-resource.html	(working copy)
@@ -0,0 +1,43 @@
+<h2 mat-dialog-title>Add ACL Resource</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="resource" class="form-label">Resource *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="resource"
+          formControlName="resource"
+          matInput
+          placeholder="Resource"
+          type="text"
+        />
+        @if (configForm.get('resource')?.invalid && configForm.get('resource')?.touched) {
+          <mat-error>
+            @if (configForm.get('resource')?.errors?.['required']) {
+              Resource is required.
+            } @else if (configForm.get('resource')?.errors) {
+              Invalid Resource IP format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.html	(working copy)
@@ -0,0 +1,38 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addACLResource()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ getGlobalSerial(i) }}</td>
+    </ng-container>
+    <ng-container matColumnDef="resource">
+      <th mat-header-cell *matHeaderCellDef> Resource</th>
+      <td mat-cell *matCellDef="let element">{{ element?.resource }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteResource(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnAclResources } from './vpn-acl-resources';
+
+describe('VpnAclResources', () => {
+  let component: VpnAclResources;
+  let fixture: ComponentFixture<VpnAclResources>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnAclResources]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnAclResources);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-resources/vpn-acl-resources.ts	(working copy)
@@ -0,0 +1,199 @@
+import {ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {MatTableDataSource} from "@angular/material/table";
+import {SharedModule} from '../../../shared/shared-module';
+import {take} from 'rxjs/operators';
+import {VpnService} from '../../../services/vpn-service';
+import {ActivatedRoute} from '@angular/router';
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {CustomValidators} from '../../../utils/custom-validators';
+
+@Component({
+  selector: 'app-vpn-acl-resources',
+  imports: [
+    SharedModule
+  ],
+  templateUrl: './vpn-acl-resources.html',
+  styleUrl: './vpn-acl-resources.scss'
+})
+export class VpnAclResources implements OnInit {
+
+  deviceName: string = '';
+  serviceName: string = '';
+  groupName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'resource', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    this.groupName = this._route.snapshot.paramMap.get('groupName') || '';
+  }
+
+  ngOnInit() {
+    setTimeout(() => {
+      this.dataSource.paginator = this.paginator;
+      this.getACLResources()
+    })
+  }
+
+  getACLResources(): void {
+    let payload: any = {
+      cmd: `show acl resource ${this.groupName}`,
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          result.contents.forEach((item: any) => {
+            if (item?.cmdid === 'acl resource') {
+              this.dataSource.data.push({
+                resource: item?.resource,
+                editable: true,
+              })
+            }
+            if (item?.cmdid === '##acl resource') {
+              this.dataSource.data.push({
+                resource: item?.resource,
+                editable: false,
+              })
+            }
+          })
+        }
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+  addACLResource() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      groupName: this.groupName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateACLResourceDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getACLResources();
+      }
+    })
+  }
+
+  deleteResource(_resource: any) {
+    let confirmMsg = `Are you sure you want to delete the resource - ${_resource?.resource}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_resource?.resource}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let payload: any = {
+          cmd: `no acl resource "${this.groupName}" "${_resource?.resource}"`,
+          vsite_name: this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, payload)
+          .pipe(take(1)).subscribe({
+          next: (result: any) => {
+            if (result && result.contents === '') {
+              this._notification.showSuccess(`The ACL Resource has been deleted successfully.`);
+              this.getACLResources();
+            }
+            this._cdRef.detectChanges();
+          },
+          error: (error: any) => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'create-acl-resource',
+  templateUrl: './create-acl-resource.html',
+  imports: [SharedModule]
+})
+export class CreateACLResourceDialog implements OnInit {
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateACLResourceDialog>);
+
+  configForm!: FormGroup;
+
+  constructor(
+    private _vpn: VpnService,
+    private _cdRef: ChangeDetectorRef,
+    private _fB: FormBuilder,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.configForm = this._fB.group({
+      resource: ['', [Validators.required, CustomValidators.ipv4()]],
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      cmd: `acl resource "${this.data?.groupName}" "${this.configForm.value.resource}"`,
+      vsite_name: this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents === '') {
+          this._notification.showSuccess(`The ACL Resource has been created successfully.`);
+          this.dialogRef.close(true);
+        }
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/create-acl-rule.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/create-acl-rule.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/create-acl-rule.html	(working copy)
@@ -0,0 +1,102 @@
+<h2 mat-dialog-title>Add ACL Rule</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="target_type" class="form-label">Target Type *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="target_type">
+          @for (_type of targetTypes; track _type) {
+            <mat-option [value]="_type?.value">{{ _type?.label }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('target_type')?.invalid && configForm.get('target_type')?.touched) {
+          <mat-error>
+            @if (configForm.get('target_type')?.errors?.['required']) {
+              Type is required.
+            } @else {
+              Invalid type format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="target_name" class="form-label">Target Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="target_name"
+          formControlName="target_name"
+          matInput
+          placeholder="Target Name"
+          type="text"
+        />
+        @if (configForm.get('target_name')?.invalid && configForm.get('target_name')?.touched) {
+          <mat-error>
+            @if (configForm.get('target_name')?.errors?.['required']) {
+              Target Name is required.
+            } @else if (configForm.get('target_name')?.errors) {
+              Invalid target name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="action" class="form-label">Action *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="action">
+          @for (_type of actions; track _type) {
+            <mat-option [value]="_type?.value">{{ _type?.label }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('action')?.invalid && configForm.get('action')?.touched) {
+          <mat-error>
+            @if (configForm.get('action')?.errors?.['required']) {
+              Action is required.
+            } @else {
+              Invalid action format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="priority" class="form-label">Priority *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="priority"
+          formControlName="priority"
+          matInput
+          placeholder="Priority"
+          type="number"
+        />
+        @if (configForm.get('priority')?.invalid && configForm.get('priority')?.touched) {
+          <mat-error>
+            @if (configForm.get('priority')?.errors?.['required']) {
+              Priority is required.
+            } @else if (configForm.get('priority')?.errors) {
+              Invalid Priority format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.html	(working copy)
@@ -0,0 +1,50 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addACLRule()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ getGlobalSerial(i) }}</td>
+    </ng-container>
+    <ng-container matColumnDef="type">
+      <th mat-header-cell *matHeaderCellDef> Target Type</th>
+      <td mat-cell *matCellDef="let element">{{ element?.target_type }}</td>
+    </ng-container>
+    <ng-container matColumnDef="name">
+      <th mat-header-cell *matHeaderCellDef> Target Name</th>
+      <td mat-cell *matCellDef="let element">{{ element?.target_name }}</td>
+    </ng-container>
+    <ng-container matColumnDef="raction">
+      <th mat-header-cell *matHeaderCellDef> Rule Action</th>
+      <td mat-cell *matCellDef="let element">{{ element?.action }}</td>
+    </ng-container>
+    <ng-container matColumnDef="priority">
+      <th mat-header-cell *matHeaderCellDef> Priority</th>
+      <td mat-cell *matCellDef="let element">{{ element?.priority }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteACLRule(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnAclRules } from './vpn-acl-rules';
+
+describe('VpnAclRules', () => {
+  let component: VpnAclRules;
+  let fixture: ComponentFixture<VpnAclRules>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnAclRules]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnAclRules);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-acl-rules/vpn-acl-rules.ts	(working copy)
@@ -0,0 +1,220 @@
+import {ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {MatTableDataSource} from "@angular/material/table";
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {ActivatedRoute} from '@angular/router';
+import {VpnService} from '../../../services/vpn-service';
+import {NotificationService} from '../../../services/notification';
+import {SharedModule} from '../../../shared/shared-module';
+import {take} from 'rxjs/operators';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {Confirmation} from '../../../services/confirmation';
+
+@Component({
+  selector: 'app-vpn-acl-rules',
+  imports: [
+    SharedModule
+  ],
+  templateUrl: './vpn-acl-rules.html',
+  styleUrl: './vpn-acl-rules.scss'
+})
+export class VpnAclRules implements OnInit {
+
+  deviceName: string = '';
+  serviceName: string = '';
+  groupName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'name', 'type', 'raction', 'priority', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    this.groupName = this._route.snapshot.paramMap.get('groupName') || '';
+  }
+
+  ngOnInit() {
+    setTimeout(() => {
+      this.dataSource.paginator = this.paginator;
+      this.getACLRules()
+    })
+  }
+
+  getACLRules() {
+    let payload: any = {
+      cmd: `show acl rule "" ${this.groupName}`,
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          result.contents.forEach((item: any) => {
+            if (item?.cmdid === 'acl rule') {
+              this.dataSource.data.push({
+                target_name: item?.target_name,
+                target_type: item?.target_type,
+                priority: item?.priority,
+                action: item?.action,
+                editable: true
+              })
+            }
+            if (item?.cmdid === '##acl rule') {
+              this.dataSource.data.push({
+                target_name: item?.target_name,
+                target_type: item?.target_type,
+                priority: item?.priority,
+                action: item?.action,
+                'editable': true
+              })
+            }
+          })
+        }
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+  addACLRule() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      groupName: this.groupName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateACLRuleDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getACLRules();
+      }
+    })
+  }
+
+  deleteACLRule(_rule: any) {
+    let confirmMsg = `Are you sure you want to delete the rule - ${_rule?.target_name}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_rule?.target_name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let payload: any = {
+          cmd: `no acl rule "${_rule?.target_name}" "${this.groupName}" "${_rule?.target_type}"`,
+          vsite_name: this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, payload)
+          .pipe(take(1)).subscribe({
+          next: (result: any) => {
+            if (result && result.contents === '') {
+              this._notification.showSuccess(`The ACL Rule has been deleted successfully.`);
+              this.getACLRules();
+            }
+            this._cdRef.detectChanges();
+          },
+          error: (error: any) => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'create-acl-rule',
+  templateUrl: './create-acl-rule.html',
+  imports: [SharedModule]
+})
+export class CreateACLRuleDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateACLRuleDialog>);
+
+  configForm!: FormGroup;
+
+  targetTypes: any = [
+    {value: 'R', label: 'Role'},
+    {value: 'U', label: 'User'},
+    {value: 'G', label: 'Group'},
+  ];
+
+  actions: any = [
+    {value: 'PERMIT', label: 'PERMIT'},
+    {value: 'DENY', label: 'DENY'},
+  ]
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _notification: NotificationService,
+    private _vpn: VpnService,
+    private _cdRef: ChangeDetectorRef,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._formBuilder.group({
+      target_type: ['R', Validators.required],
+      target_name: ['', Validators.required],
+      action: ['PERMIT', Validators.required],
+      priority: ['', [Validators.required, Validators.min(0)]],
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      cmd: 'acl rule "' + this.configForm.value.target_name + '" "' + this.data?.groupName + '" "'
+        + this.configForm.value.action + '" ' + this.configForm.value.priority + ' "' + this.configForm.value.target_type + '"',
+      vsite_name: this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents === '') {
+          this._notification.showSuccess(`The ACL Rule has been created successfully.`);
+          this.dialogRef.close(true);
+        }
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.html	(working copy)
@@ -0,0 +1,35 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>
+      <a class="back-to-main-page" (click)="backToVirtualSites()">
+        <fa-icon [icon]="['far', 'circle-left']"></fa-icon>
+        Virtual Site - {{ serviceName }}
+      </a>
+    </mat-card-title>
+  </mat-card-header>
+</mat-card>
+<div class="tab-container">
+  <mat-tab-group animationDuration="0ms" [selectedIndex]="selectedTabIndex" (selectedTabChange)="onTabChange($event)">
+    <mat-tab label="VPN Resource Groups">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-resource-groups/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="ACL Resource Groups">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-acl-resource-groups/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="Hardware Id">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-hardware-id/>
+        </div>
+      </ng-template>
+    </mat-tab>
+  </mat-tab-group>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnDetailsOverview } from './vpn-details-overview';
+
+describe('VpnDetailsOverview', () => {
+  let component: VpnDetailsOverview;
+  let fixture: ComponentFixture<VpnDetailsOverview>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnDetailsOverview]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnDetailsOverview);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-details-overview/vpn-details-overview.ts	(working copy)
@@ -0,0 +1,80 @@
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute, Router} from '@angular/router';
+import {MatTabChangeEvent} from '@angular/material/tabs';
+import {SharedModule} from '../../../shared/shared-module';
+import {VpnResourceGroups} from '../vpn-resource-groups/vpn-resource-groups';
+import {VpnAclResourceGroups} from '../vpn-acl-resource-groups/vpn-acl-resource-groups';
+import {VpnHardwareId} from '../vpn-hardware-id/vpn-hardware-id';
+
+@Component({
+  selector: 'app-vpn-details-overview',
+  imports: [
+    SharedModule,
+    VpnResourceGroups,
+    VpnAclResourceGroups,
+    VpnHardwareId
+  ],
+  templateUrl: './vpn-details-overview.html',
+  styleUrl: './vpn-details-overview.scss'
+})
+export class VpnDetailsOverview implements OnInit {
+
+  deviceName: string | null = '';
+  serviceName: string | null = '';
+  serviceDetails: any = null;
+
+  selectedTabIndex: number = 0;
+  private tabNames: string[] = [
+    'VPN Resource Groups',
+    'ACL Resource Groups',
+    'Hardware Id',
+  ];
+
+  constructor(
+    private _route: ActivatedRoute,
+    private _router: Router,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName');
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName');
+    this.serviceDetails = history.state.serviceDetails;
+
+    console.log(this.serviceDetails, this.deviceName, this.serviceName);
+
+    this._route.queryParams.subscribe(params => {
+      const tabParam = params['tab'];
+      if (tabParam) {
+        const index = this.tabNames.indexOf(tabParam);
+        if (index !== -1) {
+          this.selectedTabIndex = index;
+        } else {
+          this.selectedTabIndex = 0;
+        }
+      } else {
+        this.updateQueryParams(this.selectedTabIndex);
+      }
+    });
+  }
+
+  onTabChange(event: MatTabChangeEvent): void {
+    this.selectedTabIndex = event.index;
+    this.updateQueryParams(event.index);
+  }
+
+  backToVirtualSites(): void {
+    this._router.navigate(['/vpn-management']);
+  }
+
+  private updateQueryParams(tabIndex: number): void {
+    const tabName = this.tabNames[tabIndex];
+    if (tabName) {
+      this._router.navigate([], {
+        relativeTo: this._route,
+        queryParams: {tab: tabName},
+        queryParamsHandling: 'merge'
+      });
+    }
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/add-hardware-id-rule.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/add-hardware-id-rule.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/add-hardware-id-rule.html	(working copy)
@@ -0,0 +1,136 @@
+<h2 mat-dialog-title>Add Hardware Id Rule</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="hardwareId" class="form-label">Hardware Id *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="hardwareId"
+          formControlName="hardwareId"
+          matInput
+          placeholder="Hardware Id"
+          type="text"
+        />
+        @if (configForm.get('hardwareId')?.invalid && configForm.get('hardwareId')?.touched) {
+          <mat-error>
+            @if (configForm.get('hardwareId')?.errors?.['required']) {
+              Hardware Id is required.
+            } @else if (configForm.get('hardwareId')?.errors) {
+              Invalid hardware id format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="category" class="form-label">Category *</label>
+      <mat-radio-group formControlName="category">
+        <mat-radio-button value="account">Account</mat-radio-button>
+        <mat-radio-button value="group">Group</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    @if (configForm.value.category === 'account') {
+      <div class="form-field-wrapper">
+        <label for="username" class="form-label">Username *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="username"
+            formControlName="username"
+            matInput
+            placeholder="Username"
+            type="text"
+          />
+          @if (configForm.get('username')?.invalid && configForm.get('username')?.touched) {
+            <mat-error>
+              @if (configForm.get('username')?.errors?.['required']) {
+                Username is required.
+              } @else if (configForm.get('username')?.errors) {
+                Invalid username format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+    }
+    @if (configForm.value.category === 'group') {
+      <div class="form-field-wrapper">
+        <label for="groupname" class="form-label">Group Name *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="groupname"
+            formControlName="groupname"
+            matInput
+            placeholder="Group Name"
+            type="text"
+          />
+          @if (configForm.get('groupname')?.invalid && configForm.get('groupname')?.touched) {
+            <mat-error>
+              @if (configForm.get('groupname')?.errors?.['required']) {
+                Group Name is required.
+              } @else if (configForm.get('groupname')?.errors) {
+                Invalid group name format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+    }
+    <div class="form-field-wrapper">
+      <label for="status" class="form-label">Status *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="status">
+          @for (_status of statusOptions; track _status) {
+            <mat-option [value]="_status?.value">{{ _status?.label }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('status')?.invalid && configForm.get('status')?.touched) {
+          <mat-error>
+            @if (configForm.get('status')?.errors?.['required']) {
+              Status is required.
+            } @else {
+              Invalid status format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="hostname" class="form-label">Host Name</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="hostname"
+          formControlName="hostname"
+          matInput
+          placeholder="Host Name"
+          type="text"
+        />
+        @if (configForm.get('hostname')?.invalid && configForm.get('hostname')?.touched) {
+          <mat-error>
+            @if (configForm.get('hostname')?.errors?.['required']) {
+              Host Name is required.
+            } @else if (configForm.get('hostname')?.errors) {
+              Invalid host name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/sync-hardware-id-rules.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/sync-hardware-id-rules.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/sync-hardware-id-rules.html	(working copy)
@@ -0,0 +1,41 @@
+<h2 mat-dialog-title>Sync Hardware Id Rules</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="services" class="form-label">Virtual site *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="services" multiple>
+          @for (_vsite of serviceOptions; track _vsite) {
+            <mat-option [value]="_vsite">{{ _vsite?.name }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('services')?.invalid && configForm.get('services')?.touched) {
+          <mat-error>
+            @if (configForm.get('services')?.errors?.['required']) {
+              Virtual site is required.
+            } @else {
+              Invalid virtual site format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.html	(working copy)
@@ -0,0 +1,66 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addHardwareId()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ getGlobalSerial(i) }}</td>
+    </ng-container>
+    <ng-container matColumnDef="category">
+      <th mat-header-cell *matHeaderCellDef> Category</th>
+      <td mat-cell *matCellDef="let element">{{ element?.category }}</td>
+    </ng-container>
+    <ng-container matColumnDef="name">
+      <th mat-header-cell *matHeaderCellDef> Name</th>
+      <td mat-cell *matCellDef="let element">{{ element?.name }}</td>
+    </ng-container>
+    <ng-container matColumnDef="hardwareId">
+      <th mat-header-cell *matHeaderCellDef> Hardware Id</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.hardware_id }}</td>
+    </ng-container>
+    <ng-container matColumnDef="status">
+      <th mat-header-cell *matHeaderCellDef> Status</th>
+      <td mat-cell *matCellDef="let element">
+        {{ element?.status === 'approve' ? 'Approved' : element?.status === 'deny' ? 'Denied' : 'Pending' }}
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="hostname">
+      <th mat-header-cell *matHeaderCellDef> Hostname</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.host_name }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          @if (element.status === 'approve' || element.status === 'pending') {
+            <fa-icon [icon]="['far', 'xmark-circle']" class="delete-icon" (click)="denyHardwareId(element)"
+                     matTooltip="Deny"></fa-icon>&nbsp;&nbsp;
+          }
+          @if (element.status === 'deny' || element.status === 'pending') {
+            <fa-icon [icon]="['far', 'check-circle']" class="success-icon" (click)="approveHardwareId(element)"
+                     matTooltip="Approve"></fa-icon>&nbsp;&nbsp;
+          }
+          <fa-icon [icon]="['fas', 'gears']" size="lg" matTooltip="Sync"
+                   (click)="syncHardwareId(element)"></fa-icon> &nbsp;&nbsp;
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteHardwareId(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnHardwareId } from './vpn-hardware-id';
+
+describe('VpnHardwareId', () => {
+  let component: VpnHardwareId;
+  let fixture: ComponentFixture<VpnHardwareId>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnHardwareId]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnHardwareId);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-hardware-id/vpn-hardware-id.ts	(working copy)
@@ -0,0 +1,738 @@
+import {ChangeDetectorRef, Component, inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {VpnService} from '../../../services/vpn-service';
+import {take} from 'rxjs/operators';
+import {NotificationService} from '../../../services/notification';
+import {ActivatedRoute} from '@angular/router';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {SharedModule} from '../../../shared/shared-module';
+import {Confirmation} from '../../../services/confirmation';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {Subscription} from 'rxjs';
+import {DeviceService} from '../../../services/device-service';
+
+@Component({
+  selector: 'app-vpn-hardware-id',
+  imports: [
+    SharedModule,
+  ],
+  templateUrl: './vpn-hardware-id.html',
+  styleUrl: './vpn-hardware-id.scss'
+})
+export class VpnHardwareId implements OnInit {
+
+  deviceName: string = '';
+  serviceName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'category', 'name', 'hardwareId', 'status', 'hostname', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  hardwarePayload: any = {
+    start: 0,
+    number: 200,
+    search: {},
+    sort_predicate: "",
+    sort_reverse: ""
+  }
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  constructor(
+    private _vpn: VpnService,
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation,
+  ) {
+  }
+
+  ngOnInit() {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    setTimeout(() => {
+      this.dataSource.paginator = this.paginator;
+      this.getHardwareId();
+    })
+  }
+
+  addHardwareId() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(AddHardwareIdRuleDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getHardwareId();
+      }
+    })
+  }
+
+  getHardwareId() {
+    // ToDo: Fix the pagination
+    let payload = this.buildHardwareIdRuleCommand(this.hardwarePayload, this.serviceName);
+    this._vpn.getHardwareIds(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (res: any) => {
+        if (res && res.contents && res.contents.length > 0) {
+          const conf_list: any[] = res['contents'];
+          let total = 0;
+          const result: any[] = [];
+
+          conf_list.forEach((data: any) => {
+            if (data.cmdid.includes('#total count of hardwareid rule is')) {
+              const tmp = data.cmdid.split(' ');
+              total = parseInt(tmp[tmp.length - 1], 10);
+            }
+
+            if (data.cmdid === 'localdb hardwareid account' && data.hardwareid_value) {
+              result.push({
+                hardware_id: data.hardwareid_value,
+                category: 'account',
+                name: data.user_name || 'N/A',
+                status: data.status,
+                host_name: data.hostname
+              });
+              return;
+            }
+
+            if (data.cmdid === 'localdb hardwareid group' && data.hardwareid_value) {
+              result.push({
+                hardware_id: data.hardwareid_value,
+                category: 'group',
+                name: data.group_name || 'N/A',
+                status: data.status,
+                host_name: data.hostname
+              });
+              return;
+            }
+          });
+          this.dataSource.data = result;
+          this.dataSource.paginator = this.paginator;
+          this.totalRecords = result.length;
+        }
+        this._cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  buildHardwareIdRuleCommand(data: any, vsiteName: string) {
+    let cmd = 'show localdb hardwareid rule';
+
+    // Category
+    cmd += data.search?.category ? ` ${data.search.category}` : ' all';
+
+    // Status
+    cmd += data.search?.status ? ` ${data.search.status}` : ' all';
+
+    // Search term
+    cmd += data.search?.search ? ` "${data.search.search}" SUBSTRING` : ' "" SUBSTRING';
+
+    // Pagination
+    cmd += ` ${data.start} ${data.number}`;
+
+    // Sorting
+    if (data.sort_predicate) {
+      cmd += ` "${data.sort_predicate}`;
+      if (data.sort_reverse) {
+        cmd += ' DESC"';
+      } else {
+        cmd += '"';
+      }
+    } else {
+      cmd += ' ""';
+    }
+
+    const post_data: any = {
+      cmd: cmd,
+      vsite_name: vsiteName
+    };
+
+    return post_data;
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+  approveHardwareId(_config: any) {
+    let confirmMsg = `Are you sure you want to approve it - ${_config?.name}?`
+    this._confirmation.openConfirmDialog({
+      title: `Approve ${_config?.name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Approve It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        const commandStrings: string[] = [`sw "${this.serviceName}"`];
+        const commandDictionary: { [command: string]: any } = {};
+
+        const dataDict: any = {
+          'account': 'Account',
+          'group': 'Group'
+        };
+        [_config].forEach(rule => {
+          const command = `localdb hardwareid ${rule.category} approve "${rule.name}" "${rule.hardware_id}"`;
+          commandStrings.push(command);
+          commandDictionary[command] = {
+            id: rule.hardware_id,
+            name: `${rule.name}(${dataDict[rule.category]})`
+          };
+        });
+        const postData: any = {config: commandStrings.join('\n')};
+        this._vpn.batchAGCLICommand(this.deviceName, postData)
+          .pipe(take(1)).subscribe({
+          next: (res: any) => {
+            if (res && res.contents) {
+              this._notification.showSuccess('The hardware has been approved successfully.');
+              this.getHardwareId();
+            }
+          },
+          error: error => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+
+  denyHardwareId(_config: any) {
+    let confirmMsg = `Are you sure you want to deny it - ${_config?.name}?`
+    this._confirmation.openConfirmDialog({
+      title: `Deny ${_config?.name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Deny It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        const commandStrings: string[] = [`sw "${this.serviceName}"`];
+        const commandDictionary: { [command: string]: any } = {};
+        const dataDict: any = {
+          'account': 'Account',
+          'group': 'Group'
+        };
+        [_config].forEach(rule => {
+          const command = `localdb hardwareid ${rule.category} deny "${rule.name}" "${rule.hardware_id}"`;
+          commandStrings.push(command);
+          commandDictionary[command] = {
+            id: rule.hardware_id,
+            name: `${rule.name}(${dataDict[rule.category]})`
+          };
+        });
+        const postData: any = {config: commandStrings.join('\n')};
+        this._vpn.batchAGCLICommand(this.deviceName, postData)
+          .pipe(take(1)).subscribe({
+          next: (res: any) => {
+            if (res && res.contents) {
+              this._notification.showSuccess('The hardware has been denied successfully.');
+              this.getHardwareId();
+            }
+          },
+          error: error => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+
+  syncHardwareId(_config: any) {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      base_hid: _config
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(SyncHardwareIdRuleDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getHardwareId();
+      }
+    })
+  }
+
+  deleteHardwareId(_config: any) {
+    let confirmMsg = `Are you sure you want to delete the rule - ${_config?.name}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_config?.name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        const commandStrings: string[] = [`sw "${this.serviceName}"`];
+        const commandDictionary: { [command: string]: any } = {};
+        const dataDict: any = {
+          'account': 'Account',
+          'group': 'Group'
+        };
+        [_config].forEach(rule => {
+          const command = `no localdb hardwareid ${rule.category} ${rule.status} "${rule.name}" "${rule.hardware_id}"`;
+          commandStrings.push(command);
+          commandDictionary[command] = {
+            id: rule.hardware_id,
+            name: `${rule.name}(${dataDict[rule.category]})`
+          };
+        });
+        const postData: any = {config: commandStrings.join('\n')};
+        this._vpn.batchAGCLICommand(this.deviceName, postData)
+          .pipe(take(1)).subscribe({
+          next: (res: any) => {
+            if (res && res.contents) {
+              this._notification.showSuccess('The hardware rule has been deleted successfully.');
+              this.getHardwareId();
+            }
+          },
+          error: error => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    })
+  }
+}
+
+@Component({
+  selector: 'add-hardware-id-rule',
+  templateUrl: './add-hardware-id-rule.html',
+  imports: [SharedModule]
+})
+export class AddHardwareIdRuleDialog implements OnInit, OnDestroy {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<AddHardwareIdRuleDialog>);
+
+  configForm!: FormGroup;
+
+  statusOptions: any = [
+    {value: 'pending', label: 'Pending'},
+    {value: 'approve', label: 'Approved'},
+    {value: 'deny', label: 'Denied'},
+  ];
+  private categorySubscription!: Subscription | undefined;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.configForm = this._formBuilder.group({
+      hardwareId: ['', Validators.required],
+      category: ['account', Validators.required],
+      username: [''],
+      groupname: [''],
+      status: ['pending', Validators.required],
+      hostname: [''],
+    })
+    this.setConditionalValidators(this.configForm.get('category')?.value);
+    this.categorySubscription = this.configForm.get('category')?.valueChanges.subscribe(category => {
+      this.setConditionalValidators(category);
+    });
+  }
+
+  onSubmit(): void {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let _name = this.configForm?.value?.username;
+    if (this.configForm?.value?.category === 'group') {
+      _name = this.configForm?.value?.groupname;
+    }
+    let payload = {
+      'cmd': 'localdb hardwareid ' + this.configForm?.value?.category + ' "' + this.configForm?.value?.status + '" "' + _name
+        + '" "' + this.configForm?.value?.hardwareId + '" "' + this.configForm?.value?.hostname + '"',
+      'vsite_name': this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (res: any) => {
+        if (res && res.contents === "") {
+          this._notification.showSuccess('The hardware rule has been added successfully.');
+          this.dialogRef.close(true);
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+
+  ngOnDestroy(): void {
+    if (this.categorySubscription) {
+      this.categorySubscription.unsubscribe();
+    }
+  }
+
+  private setConditionalValidators(category: string): void {
+    const usernameControl = this.configForm.get('username');
+    const groupnameControl = this.configForm.get('groupname');
+    if (!usernameControl || !groupnameControl) {
+      console.error('Form controls for username or groupname not found.');
+      return;
+    }
+    if (category === 'account') {
+      usernameControl.setValidators(Validators.required);
+      groupnameControl.clearValidators();
+    } else if (category === 'group') {
+      groupnameControl.setValidators(Validators.required);
+      usernameControl.clearValidators();
+    } else {
+      usernameControl.clearValidators();
+      groupnameControl.clearValidators();
+    }
+    usernameControl.updateValueAndValidity();
+    groupnameControl.updateValueAndValidity();
+  }
+}
+
+@Component({
+  selector: 'sync-hardware-id-rules',
+  templateUrl: './sync-hardware-id-rules.html',
+  imports: [SharedModule]
+})
+export class SyncHardwareIdRuleDialog implements OnInit, OnDestroy {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<SyncHardwareIdRuleDialog>);
+
+  configForm!: FormGroup;
+  serviceOptions: any = [];
+
+  constructor(
+    private _vpn: VpnService,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _formBuilder: FormBuilder,
+    private _cdRef: ChangeDetectorRef,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._formBuilder.group({
+      services: ['', Validators.required],
+    })
+    setTimeout(() => {
+      this.getAMPDevices();
+    })
+  }
+
+  getAMPDevices() {
+    this.serviceOptions = [];
+    this._device.getAMPDevicesList()
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.length > 0) {
+          result.forEach((_device: any) => {
+            if (_device.type.toLowerCase() === 'ag' || _device.type.toLowerCase() === 'vxag') {
+              this.getVpnServices(_device?.name, _device?.device_group);
+            }
+          })
+        }
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  getVpnServices(aGName: string, device_group: string): void {
+    let payload = {cmd: 'show virtual site config'}
+    this._vpn.getVPNServices(aGName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          let vsites = this.processVirtualSiteData(result?.contents);
+          vsites.forEach((vsite: any) => {
+            vsite.device_name = aGName;
+            vsite.device_group = device_group;
+            if (vsite.deviceName !== this.data?.deviceName && vsite?.name !== this.data?.serviceName) {
+              this.serviceOptions.push(vsite);
+            }
+          })
+        }
+        this._cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  processVirtualSiteData(confList: any[]) {
+    const vsiteDict: { [key: string]: any } = {};
+    const result: any[] = [];
+
+    confList.forEach((data: any) => {
+      const vsiteName = data.vsite_name;
+
+      if (data.cmdid === 'virtual site name') {
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].type = data.vsite_type || '';
+          vsiteDict[vsiteName].vsite_desc = data.vsite_desc || '';
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: data.vsite_type || '',
+            vsite_desc: data.vsite_desc || '',
+            ip: [],
+            ip_str: '',
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site ip') {
+        const ip = data.ip ? data.ip.slice(3, -1) : '';
+        const port = data.port || '';
+        const ipStr = `${ip}:${port}`;
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].ip.push(ipStr);
+          vsiteDict[vsiteName].ip_str += `${ipStr}, `;
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: '',
+            vsite_desc: '',
+            ip: [ipStr],
+            ip_str: `${ipStr}, `,
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site domain') {
+        const domainName = data.domain_name || '';
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].domain.push(domainName);
+          vsiteDict[vsiteName].domain_str += `${domainName}, `;
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: '',
+            vsite_desc: '',
+            ip: [],
+            ip_str: '',
+            domain: [domainName],
+            domain_str: `${domainName}, `
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      }
+    });
+    result.forEach(vsite => {
+      if (vsite.ip_str.endsWith(', ')) {
+        vsite.ip_str = vsite.ip_str.slice(0, -2);
+      }
+      if (vsite.domain_str.endsWith(', ')) {
+        vsite.domain_str = vsite.domain_str.slice(0, -2);
+      }
+    });
+
+    return result;
+  }
+
+  ngOnDestroy() {
+  }
+
+  onSubmit(): void {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    const result: string[] = [];
+    const allDict: { [key: string]: { id: string; name: string } } = {};
+    const dataDict: any = {
+      'account': 'Account',
+      'group': 'Group'
+    };
+
+    let data: any = {
+      device_name: this.data?.deviceName,
+      vsite_name: this.data?.serviceName,
+      base_hid: [this.data?.base_hid],
+      vsite: this.configForm.value?.services
+    };
+    let all_dict: any = {}
+
+    data?.base_hid.forEach((rule: any) => {
+      const cmd = `localdb hardwareid ${rule.category} "${rule.status}" "${rule.name}" "${rule.hardware_id}" "${rule.host_name}"`;
+      result.push(cmd);
+      allDict[cmd] = {
+        id: rule.hardware_id,
+        name: `${rule.name}(${dataDict[rule.category]})`
+      };
+    });
+
+    const cmdDict: { [key: string]: any } = {};
+    const cmdList: any = [];
+    data?.base_hid.forEach((rule: any) => {
+      const commandString = `localdb hardwareid ${rule.category} "${rule.status}" "${rule.name}" "${rule.hardware_id}" "${rule.host_name}"`;
+      result.push(commandString);
+      all_dict[commandString] = {
+        'id': rule.hardware_id,
+        'name': `${rule.name}(${dataDict[rule.category]})`
+      };
+    });
+
+    if (data.vsite.length) {
+      data.vsite.forEach((vsite: any) => {
+        const vsiteConf = [vsite?.name, vsite?.device_name];
+        const cmdStr = `change to "${vsiteConf[0]}"\nsw "${vsiteConf[0]}"\n${result.join('\n')}`;
+
+        if (cmdDict[vsiteConf[1]]) {
+          cmdDict[vsiteConf[1]].cmd_str += `\n${cmdStr}`;
+          cmdDict[vsiteConf[1]].vsite_list.push(vsiteConf[0]);
+        } else {
+          cmdDict[vsiteConf[1]] = {
+            cmd_str: cmdStr,
+            device_name: vsiteConf[1],
+            service_name: vsiteConf[0],
+            vsite_list: [vsiteConf[0]]
+          };
+          cmdList.push(cmdDict[vsiteConf[1]]);
+        }
+      });
+      cmdList.forEach((cmd: any) => {
+        const postData = {config: cmd.cmd_str};
+        this._vpn.batchAGCLICommand(this.data?.deviceName, postData)
+          .pipe(take(1)).subscribe({
+          next: (res: any) => {
+            let all_has_error = false;
+            const all_result_list: any[] = [];
+            if (res && res.contents) {
+              if (res['contents'] !== 'Successful') {
+                const res_list = res['contents'].split('\n');
+                const res_dict: { [key: string]: { [key: string]: string } } = {};
+                let curr_vs = '';
+                let has_error = false;
+
+                res_list.forEach((res_str: string) => {
+                  if (!res_str) {
+                    // return;
+                  }
+                  const tmp_cmd = res_str.split(':"');
+                  if (res_str.indexOf('change to') !== -1) {
+                    curr_vs = tmp_cmd[0].replace(/^change to \"|\"$/g, '');
+                    if (!(curr_vs in res_dict)) {
+                      res_dict[curr_vs] = {};
+                    }
+                    // return;
+                  }
+                  if (curr_vs && tmp_cmd.length > 1) { // Ensure tmp_cmd[1] exists
+                    let tmp_str = tmp_cmd[1].replace(/^\"|\"$/g, '');
+                    tmp_str = tmp_str.replace(/\\/g, '');
+                    res_dict[curr_vs][tmp_cmd[0]] = tmp_str;
+                    has_error = true;
+                    all_has_error = true;
+                  }
+                });
+                if (!has_error) {
+                  all_result_list.push({
+                    type: 'success',
+                    message: this.translate('Sync hardware ID rule to device "{0}" successfully', [cmd?.device_name])
+                  });
+                } else {
+                  cmd?.vsite_list.forEach((vs: string) => {
+                    if (!res_dict[vs] || Object.keys(res_dict[vs]).length === 0) {
+                      all_result_list.push({
+                        type: 'success',
+                        message: this.translate('Sync hardware ID rule to device "{0}" virtual site "{1}" successfully', [cmd?.device_name, vs])
+                      });
+                    } else {
+                      all_result_list.push({
+                        type: 'normal',
+                        message: this.translate('Do Syncing on device "{0}" virtual site "{1}"', [cmd?.device_name, vs])
+                      });
+
+                      const commandsToCheck = Object.keys(all_dict);
+                      commandsToCheck.forEach((_cmd: string) => {
+                        if (res_dict[vs] && res_dict[vs][_cmd]) {
+                          all_result_list.push({
+                            type: 'error',
+                            message: this.translate('Create hardware ID rule for "{0}" ID "{1}" failed', [all_dict[_cmd]['name'], all_dict[_cmd]['id']]) + ': ' + res_dict[vs][_cmd]
+                          });
+                        } else if (all_dict[_cmd]) {
+                          all_result_list.push({
+                            type: 'success',
+                            message: this.translate('Create hardware ID rule for "{0}" ID "{1}" successfully', [all_dict[_cmd]['name'], all_dict[_cmd]['id']])
+                          });
+                        }
+                      });
+                    }
+                  });
+                }
+              }
+            } else {
+              all_has_error = true;
+              all_result_list.push({
+                type: 'error',
+                message: this.translate('Sync hardware ID rule to device "{0}" failed: internal error', [cmd?.device_name])
+              });
+            }
+            all_result_list.forEach((msg: any) => {
+              if (msg?.type === 'normal' || msg?.type === 'success') {
+                this._notification.showSuccess(msg?.message);
+              } else {
+                this._notification.showError(msg?.message);
+              }
+            })
+          },
+          error: error => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      })
+    }
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+
+  private translate(key: string, params?: any[]): string {
+    let translatedString = key;
+    if (params) {
+      params.forEach((param, index) => {
+        translatedString = translatedString.replace(`{${index}}`, param);
+      });
+    }
+    return translatedString;
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/create-vpn-resource-group-app-excluded-resource.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/create-vpn-resource-group-app-excluded-resource.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/create-vpn-resource-group-app-excluded-resource.html	(working copy)
@@ -0,0 +1,64 @@
+<h2 mat-dialog-title>Add Application-Type VPN Resource Excluded Item</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="appname" class="form-label">Application Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="appname"
+          formControlName="appname"
+          matInput
+          placeholder="Application Name"
+          type="text"
+        />
+        @if (configForm.get('appname')?.invalid && configForm.get('appname')?.touched) {
+          <mat-error>
+            @if (configForm.get('appname')?.errors?.['required']) {
+              Application Name is required.
+            } @else if (configForm.get('appname')?.errors) {
+              Invalid application name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="filename" class="form-label">File Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="filename"
+          formControlName="filename"
+          matInput
+          placeholder="File Name"
+          type="text"
+        />
+        @if (configForm.get('filename')?.invalid && configForm.get('filename')?.touched) {
+          <mat-error>
+            @if (configForm.get('filename')?.errors?.['required']) {
+              File Name is required.
+            } @else if (configForm.get('filename')?.errors) {
+              Invalid file name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.html	(working copy)
@@ -0,0 +1,42 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addAppVPNResource()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ i | globalSerial: paginator }}</td>
+    </ng-container>
+    <ng-container matColumnDef="appName">
+      <th mat-header-cell *matHeaderCellDef> Application Name</th>
+      <td mat-cell *matCellDef="let element">{{ element?.appname }}</td>
+    </ng-container>
+    <ng-container matColumnDef="fileName">
+      <th mat-header-cell *matHeaderCellDef> File Name</th>
+      <td mat-cell *matCellDef="let element">{{ element?.exename }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteAppVPNResource(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnResourceGroupAppResourcesExcluded } from './vpn-resource-group-app-resources-excluded';
+
+describe('VpnResourceGroupAppResourcesExcluded', () => {
+  let component: VpnResourceGroupAppResourcesExcluded;
+  let fixture: ComponentFixture<VpnResourceGroupAppResourcesExcluded>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnResourceGroupAppResourcesExcluded]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnResourceGroupAppResourcesExcluded);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded.ts	(working copy)
@@ -0,0 +1,175 @@
+import {ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {ActivatedRoute} from '@angular/router';
+import {VpnService} from '../../../services/vpn-service';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {SharedModule} from '../../../shared/shared-module';
+import {take} from 'rxjs/operators';
+import {GlobalSerialPipe} from '../../../pipes/global-serial-pipe';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+
+@Component({
+  selector: 'app-vpn-resource-group-app-resources-excluded',
+  imports: [SharedModule, GlobalSerialPipe],
+  templateUrl: './vpn-resource-group-app-resources-excluded.html',
+  styleUrl: './vpn-resource-group-app-resources-excluded.scss'
+})
+export class VpnResourceGroupAppResourcesExcluded implements OnInit {
+  deviceName: string = '';
+  serviceName: string = '';
+  groupName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'appName', 'fileName', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    this.groupName = this._route.snapshot.paramMap.get('groupName') || '';
+  }
+
+  ngOnInit() {
+    this.dataSource.paginator = this.paginator;
+    setTimeout(() => {
+      this.getAppVPNResource();
+    })
+  }
+
+  getAppVPNResource() {
+    let payload: any = {
+      cmd: `show vpn resource groupexcludeditem appname "${this.groupName}"`,
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          this.dataSource.data = result.contents;
+        }
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  addAppVPNResource() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      groupName: this.groupName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateVPNResourceAppExcludedDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getAppVPNResource();
+      }
+    })
+  }
+
+  deleteAppVPNResource(_resource: any) {
+    let confirmMsg = `Are you sure you want to delete the resource - ${_resource?.appname}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_resource?.appname}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let payload: any = {
+          cmd: `no vpn resource groupexcludeditem appname "${this.groupName}" "${_resource?.appname}"`,
+          vsite_name: this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, payload)
+          .pipe(take(1)).subscribe({
+          next: (result: any) => {
+            if (result && result.contents === '') {
+              this._notification.showSuccess(`The vpn resource has been deleted successfully.`);
+              this.getAppVPNResource();
+            }
+            this._cdRef.detectChanges();
+          },
+          error: (error: any) => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'create-vpn-resource-group-app-excluded-resource',
+  templateUrl: './create-vpn-resource-group-app-excluded-resource.html',
+  imports: [SharedModule]
+})
+export class CreateVPNResourceAppExcludedDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateVPNResourceAppExcludedDialog>);
+
+  configForm!: FormGroup;
+
+  constructor(
+    private _fB: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._fB.group({
+      appname: ['', Validators.required],
+      filename: ['', Validators.required]
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      cmd: `vpn resource groupexcludeditem appname "${this.data?.groupName}" "${this.configForm.value?.appname}" "${this.configForm.value?.filename}"`,
+      vsite_name: this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents === '') {
+          this._notification.showSuccess(`The VPN resource has been created successfully.`);
+          this.dialogRef.close(true);
+        }
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/create-vpn-resource-group-app-resource.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/create-vpn-resource-group-app-resource.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/create-vpn-resource-group-app-resource.html	(working copy)
@@ -0,0 +1,85 @@
+<h2 mat-dialog-title>Add Application-Type VPN Resource Item</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="appname" class="form-label">Application Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="appname"
+          formControlName="appname"
+          matInput
+          placeholder="Application Name"
+          type="text"
+        />
+        @if (configForm.get('appname')?.invalid && configForm.get('appname')?.touched) {
+          <mat-error>
+            @if (configForm.get('appname')?.errors?.['required']) {
+              Application Name is required.
+            } @else if (configForm.get('appname')?.errors) {
+              Invalid application name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="filename" class="form-label">File Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="filename"
+          formControlName="filename"
+          matInput
+          placeholder="File Name"
+          type="text"
+        />
+        @if (configForm.get('filename')?.invalid && configForm.get('filename')?.touched) {
+          <mat-error>
+            @if (configForm.get('filename')?.errors?.['required']) {
+              File Name is required.
+            } @else if (configForm.get('filename')?.errors) {
+              Invalid file name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="md5" class="form-label">MD5 Hash *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="md5"
+          formControlName="md5"
+          matInput
+          placeholder="MD5 Hash"
+          type="text"
+        />
+        @if (configForm.get('md5')?.invalid && configForm.get('md5')?.touched) {
+          <mat-error>
+            @if (configForm.get('md5')?.errors?.['required']) {
+              MD5 Hash is required.
+            } @else if (configForm.get('md5')?.errors) {
+              Invalid MD5 hash format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.html	(working copy)
@@ -0,0 +1,46 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addAppVPNResource()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ i | globalSerial: paginator }}</td>
+    </ng-container>
+    <ng-container matColumnDef="appName">
+      <th mat-header-cell *matHeaderCellDef> Application Name</th>
+      <td mat-cell *matCellDef="let element">{{ element?.appname }}</td>
+    </ng-container>
+    <ng-container matColumnDef="fileName">
+      <th mat-header-cell *matHeaderCellDef> File Name</th>
+      <td mat-cell *matCellDef="let element">{{ element?.exename }}</td>
+    </ng-container>
+    <ng-container matColumnDef="md5">
+      <th mat-header-cell *matHeaderCellDef> MD5 Hash value</th>
+      <td mat-cell *matCellDef="let element">{{ element?.hash }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteAppVPNResource(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnResourceGroupAppResources } from './vpn-resource-group-app-resources';
+
+describe('VpnResourceGroupAppResources', () => {
+  let component: VpnResourceGroupAppResources;
+  let fixture: ComponentFixture<VpnResourceGroupAppResources>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnResourceGroupAppResources]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnResourceGroupAppResources);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-app-resources/vpn-resource-group-app-resources.ts	(working copy)
@@ -0,0 +1,176 @@
+import {ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../../shared/shared-module';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {ActivatedRoute} from '@angular/router';
+import {VpnService} from '../../../services/vpn-service';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {take} from 'rxjs/operators';
+import {GlobalSerialPipe} from '../../../pipes/global-serial-pipe';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+
+@Component({
+  selector: 'app-vpn-resource-group-app-resources',
+  imports: [SharedModule, GlobalSerialPipe],
+  templateUrl: './vpn-resource-group-app-resources.html',
+  styleUrl: './vpn-resource-group-app-resources.scss'
+})
+export class VpnResourceGroupAppResources implements OnInit {
+  deviceName: string = '';
+  serviceName: string = '';
+  groupName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'appName', 'fileName', 'md5', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    this.groupName = this._route.snapshot.paramMap.get('groupName') || '';
+  }
+
+  ngOnInit() {
+    this.dataSource.paginator = this.paginator;
+    setTimeout(() => {
+      this.getAppVPNResource();
+    })
+  }
+
+  getAppVPNResource() {
+    let payload: any = {
+      cmd: `show vpn resource groupitem appname "${this.groupName}"`,
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          this.dataSource.data = result.contents;
+        }
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  addAppVPNResource() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      groupName: this.groupName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateVPNResourceAppDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getAppVPNResource();
+      }
+    })
+  }
+
+  deleteAppVPNResource(_resource: any) {
+    let confirmMsg = `Are you sure you want to delete the resource - ${_resource?.appname}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_resource?.appname}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let payload: any = {
+          cmd: `no vpn resource groupitem appname "${this.groupName}" "${_resource?.appname}"`,
+          vsite_name: this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, payload)
+          .pipe(take(1)).subscribe({
+          next: (result: any) => {
+            if (result && result.contents === '') {
+              this._notification.showSuccess(`The vpn resource has been deleted successfully.`);
+              this.getAppVPNResource();
+            }
+            this._cdRef.detectChanges();
+          },
+          error: (error: any) => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'create-vpn-resource-group-app-resource',
+  templateUrl: './create-vpn-resource-group-app-resource.html',
+  imports: [SharedModule]
+})
+export class CreateVPNResourceAppDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateVPNResourceAppDialog>);
+
+  configForm!: FormGroup;
+
+  constructor(
+    private _fB: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._fB.group({
+      appname: ['', Validators.required],
+      filename: ['', Validators.required],
+      md5: ['', Validators.required],
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      cmd: `vpn resource groupitem appname "${this.data?.groupName}" "${this.configForm.value?.appname}" "${this.configForm.value?.filename}" "${this.configForm.value?.md5}"`,
+      vsite_name: this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents === '') {
+          this._notification.showSuccess(`The VPN resource has been created successfully.`);
+          this.dialogRef.close(true);
+        }
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/create-vpn-resource-group-network-excluded-resource.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/create-vpn-resource-group-network-excluded-resource.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/create-vpn-resource-group-network-excluded-resource.html	(working copy)
@@ -0,0 +1,62 @@
+<h2 mat-dialog-title>Add Network-Type VPN Resource Excluded Item</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="appname" class="form-label">Application Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="appname"
+          formControlName="appname"
+          matInput
+          placeholder="Application Name"
+          type="text"
+        />
+        @if (configForm.get('appname')?.invalid && configForm.get('appname')?.touched) {
+          <mat-error>
+            @if (configForm.get('appname')?.errors?.['required']) {
+              Application Name is required.
+            } @else if (configForm.get('appname')?.errors) {
+              Invalid application name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="type" class="form-label">Type *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="type">
+          @for (_type of resourceTypes; track _type) {
+            <mat-option [value]="_type?.value">{{ _type?.label }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('type')?.invalid && configForm.get('type')?.touched) {
+          <mat-error>
+            @if (configForm.get('type')?.errors?.['required']) {
+              Type is required.
+            } @else {
+              Invalid type format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.html	(working copy)
@@ -0,0 +1,42 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addNetworkVPNResource()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ i | globalSerial: paginator }}</td>
+    </ng-container>
+    <ng-container matColumnDef="resource">
+      <th mat-header-cell *matHeaderCellDef> Network Resource</th>
+      <td mat-cell *matCellDef="let element">{{ element?.netresource }}</td>
+    </ng-container>
+    <ng-container matColumnDef="type">
+      <th mat-header-cell *matHeaderCellDef> Resource Tye</th>
+      <td mat-cell *matCellDef="let element">{{ resourceType[element?.type] }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteNetworkVPNResource(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnResourceGroupNetworkResourcesExcluded } from './vpn-resource-group-network-resources-excluded';
+
+describe('VpnResourceGroupNetworkResourcesExcluded', () => {
+  let component: VpnResourceGroupNetworkResourcesExcluded;
+  let fixture: ComponentFixture<VpnResourceGroupNetworkResourcesExcluded>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnResourceGroupNetworkResourcesExcluded]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnResourceGroupNetworkResourcesExcluded);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded.ts	(working copy)
@@ -0,0 +1,191 @@
+import {ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../../shared/shared-module';
+import {GlobalSerialPipe} from '../../../pipes/global-serial-pipe';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {ActivatedRoute} from '@angular/router';
+import {VpnService} from '../../../services/vpn-service';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {take} from 'rxjs/operators';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+
+@Component({
+  selector: 'app-vpn-resource-group-network-resources-excluded',
+  imports: [    SharedModule,
+    GlobalSerialPipe,],
+  templateUrl: './vpn-resource-group-network-resources-excluded.html',
+  styleUrl: './vpn-resource-group-network-resources-excluded.scss'
+})
+export class VpnResourceGroupNetworkResourcesExcluded implements OnInit {
+  deviceName: string = '';
+  serviceName: string = '';
+  groupName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'resource', 'type', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  resourceType: any = {
+    0: 'Both',
+    1: 'Network',
+    2: 'Application'
+  }
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    this.groupName = this._route.snapshot.paramMap.get('groupName') || '';
+  }
+
+  ngOnInit() {
+    this.dataSource.paginator = this.paginator;
+    setTimeout(() => {
+      this.getNetworkVPNResource();
+    })
+  }
+
+  getNetworkVPNResource() {
+    let payload: any = {
+      cmd: `show vpn resource groupexcludeditem network "${this.groupName}"`,
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          this.dataSource.data = result.contents;
+        }
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  addNetworkVPNResource() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      groupName: this.groupName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateVPNResourceNetworkExcludedDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getNetworkVPNResource();
+      }
+    })
+  }
+
+  deleteNetworkVPNResource(_resource: any) {
+    let confirmMsg = `Are you sure you want to delete the resource - ${_resource?.netresource}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_resource?.netresource}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let payload: any = {
+          cmd: `no vpn resource groupexcludeditem network "${this.groupName}" "${_resource?.netresource}"`,
+          vsite_name: this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, payload)
+          .pipe(take(1)).subscribe({
+          next: (result: any) => {
+            if (result && result.contents === '') {
+              this._notification.showSuccess(`The vpn resource has been deleted successfully.`);
+              this.getNetworkVPNResource();
+            }
+            this._cdRef.detectChanges();
+          },
+          error: (error: any) => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'create-vpn-resource-group-network-excluded-resource',
+  templateUrl: './create-vpn-resource-group-network-excluded-resource.html',
+  imports: [SharedModule]
+})
+export class CreateVPNResourceNetworkExcludedDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateVPNResourceNetworkExcludedDialog>);
+
+  configForm!: FormGroup;
+
+  resourceTypes: any = [
+    { value: 0, label: 'Both' },
+    { value: 1, label: 'Network' },
+    { value: 2, label: 'Application' },
+  ]
+
+  constructor(
+    private _fB: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._fB.group({
+      appname: ['', Validators.required],
+      type: [0, Validators.required],
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      cmd: `vpn resource groupexcludeditem network "${this.data?.groupName}" "${this.configForm.value?.appname}" ${this.configForm.value?.type}`,
+      vsite_name: this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents === '') {
+          this._notification.showSuccess(`The VPN resource has been created successfully.`);
+          this.dialogRef.close(true);
+        } else {
+          this._notification.showError(`Error: ${result?.contents}`);
+        }
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
+
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/create-vpn-resource-group-network-resource.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/create-vpn-resource-group-network-resource.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/create-vpn-resource-group-network-resource.html	(working copy)
@@ -0,0 +1,62 @@
+<h2 mat-dialog-title>Add Network-Type VPN Resource Item</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="appname" class="form-label">Application Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="appname"
+          formControlName="appname"
+          matInput
+          placeholder="Application Name"
+          type="text"
+        />
+        @if (configForm.get('appname')?.invalid && configForm.get('appname')?.touched) {
+          <mat-error>
+            @if (configForm.get('appname')?.errors?.['required']) {
+              Application Name is required.
+            } @else if (configForm.get('appname')?.errors) {
+              Invalid application name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="type" class="form-label">Type *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="type">
+          @for (_type of resourceTypes; track _type) {
+            <mat-option [value]="_type?.value">{{ _type?.label }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('type')?.invalid && configForm.get('type')?.touched) {
+          <mat-error>
+            @if (configForm.get('type')?.errors?.['required']) {
+              Type is required.
+            } @else {
+              Invalid type format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.html	(working copy)
@@ -0,0 +1,42 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addNetworkVPNResource()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ i | globalSerial: paginator }}</td>
+    </ng-container>
+    <ng-container matColumnDef="resource">
+      <th mat-header-cell *matHeaderCellDef> Network Resource</th>
+      <td mat-cell *matCellDef="let element">{{ element?.netresource }}</td>
+    </ng-container>
+    <ng-container matColumnDef="type">
+      <th mat-header-cell *matHeaderCellDef> Resource Tye</th>
+      <td mat-cell *matCellDef="let element">{{ resourceType[element?.type] }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteNetworkVPNResource(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnResourceGroupNetworkResources } from './vpn-resource-group-network-resources';
+
+describe('VpnResourceGroupNetworkResources', () => {
+  let component: VpnResourceGroupNetworkResources;
+  let fixture: ComponentFixture<VpnResourceGroupNetworkResources>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnResourceGroupNetworkResources]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnResourceGroupNetworkResources);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-network-resources/vpn-resource-group-network-resources.ts	(working copy)
@@ -0,0 +1,192 @@
+import {ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {GlobalSerialPipe} from "../../../pipes/global-serial-pipe";
+import {MatTableDataSource} from "@angular/material/table";
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {ActivatedRoute} from '@angular/router';
+import {VpnService} from '../../../services/vpn-service';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {take} from 'rxjs/operators';
+import {SharedModule} from '../../../shared/shared-module';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+
+@Component({
+  selector: 'app-vpn-resource-group-network-resources',
+  imports: [
+    SharedModule,
+    GlobalSerialPipe,
+  ],
+  templateUrl: './vpn-resource-group-network-resources.html',
+  styleUrl: './vpn-resource-group-network-resources.scss'
+})
+export class VpnResourceGroupNetworkResources implements OnInit {
+  deviceName: string = '';
+  serviceName: string = '';
+  groupName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'resource', 'type', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  resourceType: any = {
+    0: 'Both',
+    1: 'Network',
+    2: 'Application'
+  }
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+    this.groupName = this._route.snapshot.paramMap.get('groupName') || '';
+  }
+
+  ngOnInit() {
+    this.dataSource.paginator = this.paginator;
+    setTimeout(() => {
+      this.getNetworkVPNResource();
+    })
+  }
+
+  getNetworkVPNResource() {
+    let payload: any = {
+      cmd: `show vpn resource groupitem network "${this.groupName}"`,
+      vsite_name: this.serviceName
+    }
+    this.dataSource.data = [];
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          this.dataSource.data = result.contents;
+        }
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  addNetworkVPNResource() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      groupName: this.groupName,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateVPNResourceNetworkDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getNetworkVPNResource();
+      }
+    })
+  }
+
+  deleteNetworkVPNResource(_resource: any) {
+    let confirmMsg = `Are you sure you want to delete the resource - ${_resource?.netresource}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_resource?.netresource}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let payload: any = {
+          cmd: `no vpn resource groupitem network "${this.groupName}" "${_resource?.netresource}"`,
+          vsite_name: this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, payload)
+          .pipe(take(1)).subscribe({
+          next: (result: any) => {
+            if (result && result.contents === '') {
+              this._notification.showSuccess(`The vpn resource has been deleted successfully.`);
+              this.getNetworkVPNResource();
+            }
+            this._cdRef.detectChanges();
+          },
+          error: (error: any) => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'create-vpn-resource-group-network-resource',
+  templateUrl: './create-vpn-resource-group-network-resource.html',
+  imports: [SharedModule]
+})
+export class CreateVPNResourceNetworkDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateVPNResourceNetworkDialog>);
+
+  configForm!: FormGroup;
+
+  resourceTypes: any = [
+    { value: 0, label: 'Both' },
+    { value: 1, label: 'Network' },
+    { value: 2, label: 'Application' },
+  ]
+
+  constructor(
+    private _fB: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._fB.group({
+      appname: ['', Validators.required],
+      type: [0, Validators.required],
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      cmd: `vpn resource groupitem network "${this.data?.groupName}" "${this.configForm.value?.appname}" ${this.configForm.value?.type}`,
+      vsite_name: this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents === '') {
+          this._notification.showSuccess(`The VPN resource has been created successfully.`);
+          this.dialogRef.close(true);
+        } else {
+          this._notification.showError(`Error: ${result?.contents}`);
+        }
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.html	(working copy)
@@ -0,0 +1,42 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>
+      <a class="back-to-main-page" (click)="backToVPNResourceGroups()">
+        <fa-icon [icon]="['far', 'circle-left']"></fa-icon>
+        Virtual Site - {{ serviceName }} / VPN Resource Group - {{ groupName }}
+      </a>
+    </mat-card-title>
+  </mat-card-header>
+</mat-card>
+<div class="tab-container">
+  <mat-tab-group animationDuration="0ms" [selectedIndex]="selectedTabIndex" (selectedTabChange)="onTabChange($event)">
+    <mat-tab label="Application-Type VPN Resources">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-resource-group-app-resources/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="Application-Type VPN Resources Excluded">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-resource-group-app-resources-excluded/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="Network-Type VPN Resources">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-resource-group-network-resources/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="Network-Type VPN Resources Excluded">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vpn-resource-group-network-resources-excluded/>
+        </div>
+      </ng-template>
+    </mat-tab>
+  </mat-tab-group>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnResourceGroupOverview } from './vpn-resource-group-overview';
+
+describe('VpnResourceGroupOverview', () => {
+  let component: VpnResourceGroupOverview;
+  let fixture: ComponentFixture<VpnResourceGroupOverview>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnResourceGroupOverview]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnResourceGroupOverview);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-group-overview/vpn-resource-group-overview.ts	(working copy)
@@ -0,0 +1,92 @@
+import { Component } from '@angular/core';
+import {FaIconComponent} from '@fortawesome/angular-fontawesome';
+import {MatCard, MatCardHeader, MatCardTitle} from '@angular/material/card';
+import {MatTab, MatTabChangeEvent, MatTabContent, MatTabGroup} from '@angular/material/tabs';
+import {VpnAclResources} from '../vpn-acl-resources/vpn-acl-resources';
+import {VpnAclRules} from '../vpn-acl-rules/vpn-acl-rules';
+import {ActivatedRoute, Router} from '@angular/router';
+import {SharedModule} from '../../../shared/shared-module';
+import {VpnResourceGroupAppResources} from '../vpn-resource-group-app-resources/vpn-resource-group-app-resources';
+import {
+  VpnResourceGroupAppResourcesExcluded
+} from '../vpn-resource-group-app-resources-excluded/vpn-resource-group-app-resources-excluded';
+import {
+  VpnResourceGroupNetworkResources
+} from '../vpn-resource-group-network-resources/vpn-resource-group-network-resources';
+import {
+  VpnResourceGroupNetworkResourcesExcluded
+} from '../vpn-resource-group-network-resources-excluded/vpn-resource-group-network-resources-excluded';
+
+@Component({
+  selector: 'app-vpn-resource-group-overview',
+  imports: [
+    SharedModule,
+    VpnResourceGroupAppResources,
+    VpnResourceGroupAppResourcesExcluded,
+    VpnResourceGroupNetworkResources,
+    VpnResourceGroupNetworkResourcesExcluded
+  ],
+  templateUrl: './vpn-resource-group-overview.html',
+  styleUrl: './vpn-resource-group-overview.scss'
+})
+export class VpnResourceGroupOverview {
+  deviceName: string | null = '';
+  serviceName: string | null = '';
+  groupName: string | null = '';
+  serviceDetails: any = null;
+
+  selectedTabIndex: number = 0;
+  private tabNames: string[] = [
+    'Application-Type VPN Resources',
+    'Application-Type VPN Resources Excluded',
+    'Network-Type VPN Resources',
+    'Network-Type VPN Resources Excluded',
+  ];
+
+  constructor(
+    private _route: ActivatedRoute,
+    private _router: Router,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName');
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName');
+    this.groupName = this._route.snapshot.paramMap.get('groupName');
+    this.serviceDetails = history.state.serviceDetails;
+
+    this._route.queryParams.subscribe(params => {
+      const tabParam = params['tab'];
+      if (tabParam) {
+        const index = this.tabNames.indexOf(tabParam);
+        if (index !== -1) {
+          this.selectedTabIndex = index;
+        } else {
+          this.selectedTabIndex = 0;
+        }
+      } else {
+        this.updateQueryParams(this.selectedTabIndex);
+      }
+    });
+  }
+
+  onTabChange(event: MatTabChangeEvent): void {
+    this.selectedTabIndex = event.index;
+    this.updateQueryParams(event.index);
+  }
+
+  backToVPNResourceGroups(): void {
+    this._router.navigate(['/vpn-management/details', this.deviceName, this.serviceName]);
+  }
+
+  private updateQueryParams(tabIndex: number): void {
+    const tabName = this.tabNames[tabIndex];
+    if (tabName) {
+      this._router.navigate([], {
+        relativeTo: this._route,
+        queryParams: {tab: tabName},
+        queryParamsHandling: 'merge'
+      });
+    }
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/create-vpn-resource-group.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/create-vpn-resource-group.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/create-vpn-resource-group.html	(working copy)
@@ -0,0 +1,62 @@
+<h2 mat-dialog-title>Add ACL Resource Group</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="group_name" class="form-label">Group Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="group_name"
+          formControlName="group_name"
+          matInput
+          placeholder="Group Name"
+          type="text"
+        />
+        @if (configForm.get('group_name')?.invalid && configForm.get('group_name')?.touched) {
+          <mat-error>
+            @if (configForm.get('group_name')?.errors?.['required']) {
+              Group name is required.
+            } @else if (configForm.get('group_name')?.errors) {
+              Invalid group name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="role_name" class="form-label">Associated Role</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="role_name" multiple>
+          @for (_role of data?.roles; track _role) {
+            <mat-option [value]="_role?.role_name">{{ _role?.role_name }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('role_name')?.invalid && configForm.get('role_name')?.touched) {
+          <mat-error>
+            @if (configForm.get('role_name')?.errors?.['required']) {
+              Role is required.
+            } @else {
+              Invalid role format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/sync-vpn-resource-group.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/sync-vpn-resource-group.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/sync-vpn-resource-group.html	(working copy)
@@ -0,0 +1,41 @@
+<h2 mat-dialog-title>Sync VPN Resource Groups</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="services" class="form-label">Virtual site *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="services" multiple>
+          @for (_vsite of serviceOptions; track _vsite) {
+            <mat-option [value]="_vsite">{{ _vsite?.name }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('services')?.invalid && configForm.get('services')?.touched) {
+          <mat-error>
+            @if (configForm.get('services')?.errors?.['required']) {
+              Virtual site is required.
+            } @else {
+              Invalid virtual site format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/update-vpn-resource-group.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/update-vpn-resource-group.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/update-vpn-resource-group.html	(working copy)
@@ -0,0 +1,62 @@
+<h2 mat-dialog-title>Update ACL Resource Group</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="configForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="group_name" class="form-label">Group Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="group_name"
+          formControlName="group_name"
+          matInput
+          placeholder="Group Name"
+          type="text"
+        />
+        @if (configForm.get('group_name')?.invalid && configForm.get('group_name')?.touched) {
+          <mat-error>
+            @if (configForm.get('group_name')?.errors?.['required']) {
+              Group name is required.
+            } @else if (configForm.get('group_name')?.errors) {
+              Invalid group name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="role_name" class="form-label">Associated Role</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="role_name" multiple>
+          @for (_role of data?.roles; track _role) {
+            <mat-option [value]="_role?.role_name">{{ _role?.role_name }}</mat-option>
+          }
+        </mat-select>
+        @if (configForm.get('role_name')?.invalid && configForm.get('role_name')?.touched) {
+          <mat-error>
+            @if (configForm.get('role_name')?.errors?.['required']) {
+              Role is required.
+            } @else {
+              Invalid role format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="primary"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.html	(working copy)
@@ -0,0 +1,48 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <div>
+      <button mat-raised-button (click)="addVPNResourceGroup()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ getGlobalSerial(i) }}</td>
+    </ng-container>
+    <ng-container matColumnDef="name">
+      <th mat-header-cell *matHeaderCellDef> Group Name</th>
+      <td mat-cell *matCellDef="let element">
+        <a class="details-page-link" (click)="goToDetails(element)">{{ element?.group_name }}</a>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="role">
+      <th mat-header-cell *matHeaderCellDef> Role</th>
+      <td mat-cell *matCellDef="let element">{{ element?.role_name }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          <fa-icon [icon]="['far', 'edit']" size="lg" matTooltip="Edit"
+                   (click)="updateVPNResourceGroup(element)"></fa-icon> &nbsp;&nbsp;
+          <fa-icon [icon]="['fas', 'gears']" size="lg" matTooltip="Sync"
+                   (click)="syncVPNResourceGroup(element)"></fa-icon> &nbsp;&nbsp;
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete"
+                   (click)="deleteVPNResourceGroup(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VpnResourceGroups } from './vpn-resource-groups';
+
+describe('VpnResourceGroups', () => {
+  let component: VpnResourceGroups;
+  let fixture: ComponentFixture<VpnResourceGroups>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VpnResourceGroups]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VpnResourceGroups);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vpn-resource-groups/vpn-resource-groups.ts	(working copy)
@@ -0,0 +1,930 @@
+import {ChangeDetectorRef, Component, inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../../shared/shared-module';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {ActivatedRoute, Router} from '@angular/router';
+import {VpnService} from '../../../services/vpn-service';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {take} from 'rxjs/operators';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {DeviceService} from '../../../services/device-service';
+
+@Component({
+  selector: 'app-vpn-resource-groups',
+  imports: [
+    SharedModule
+  ],
+  templateUrl: './vpn-resource-groups.html',
+  styleUrl: './vpn-resource-groups.scss'
+})
+export class VpnResourceGroups implements OnInit {
+  deviceName: string = '';
+  serviceName: string = '';
+  totalRecords: number = 0;
+  tableColumns: string[] = ['serial', 'name', 'role', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+  vpnGroups: any = [];
+  vpnGroupsMap: any = {}
+  roles: any = [];
+
+  constructor(
+    private _cdRef: ChangeDetectorRef,
+    private _route: ActivatedRoute,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+    private _router: Router,
+    private _confirmation: Confirmation
+  ) {
+    this.deviceName = this._route.snapshot.paramMap.get('deviceName') || '';
+    this.serviceName = this._route.snapshot.paramMap.get('serviceName') || '';
+  }
+
+  ngOnInit() {
+    this.dataSource.paginator = this.paginator;
+    setTimeout(() => {
+      this.getVPNResourceGroups();
+    })
+  }
+
+  getVPNResourceGroups() {
+    this.vpnGroups = [];
+    this.dataSource.data = [];
+    let payload: any = {'cmd': 'show vpn resource group', 'vsite_name': this.serviceName};
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          result?.contents.forEach((content: any) => {
+            if (content?.cmdid === 'vpn resource group') {
+              this.vpnGroups.push({
+                group_name: content?.resourcegroup,
+              });
+            }
+          })
+        }
+        this.dataSource.data = this.vpnGroups;
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = this.dataSource.data.length;
+        this.getVPNRoles();
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  getVPNRoles() {
+    // ToDo: Revisit this logic with AG, the CLI looks outdated.
+    let payload: any = {'cmd': 'show role resource', 'vsite_name': this.serviceName};
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          result?.contents.forEach((content: any) => {
+            if (content?.cmdid === 'role resource vpnresourcegroup') {
+              if (this.vpnGroupsMap[content?.groupname]) {
+                this.vpnGroupsMap[content?.groupname].push(content?.role_name);
+              } else {
+                this.vpnGroupsMap[content?.groupname] = [content?.role_name];
+              }
+            }
+          })
+          this.getRoles();
+          this._cdRef.detectChanges();
+        }
+
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  getRoles() {
+    this.roles = []
+    let payload: any = {'cmd': 'show role name', 'vsite_name': this.serviceName};
+    this._vpn.executeAGCLICommand(this.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          result?.contents.forEach((content: any) => {
+            this.roles.push({
+              role_name: content?.role_name,
+            });
+          })
+          this._cdRef.detectChanges();
+        }
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    });
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+  goToDetails(_group: any) {
+    this._router.navigate(['/vpn-management/details/vpn-resource', this.deviceName, this.serviceName, _group?.group_name], {
+      state: {}
+    });
+  }
+
+  addVPNResourceGroup() {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      roles: this.roles,
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(CreateVPNResourceGroupDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getVPNResourceGroups();
+      }
+    })
+  }
+
+  updateVPNResourceGroup(_group: any) {
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      roles: this.roles,
+      group_name: _group.group_name,
+      role_name: _group.role_name || [],
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(UpdateVPNResourceGroupDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getVPNResourceGroups();
+      }
+    })
+  }
+
+  deleteVPNResourceGroup(_group: any) {
+    let confirmMsg = `Are you sure you want to delete the rule - ${_group?.group_name}?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_group?.group_name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        const postData: any = {
+          'cmd': 'no vpn resource group "' + _group.group_name + '"',
+          'vsite_name': this.serviceName
+        }
+        this._vpn.executeAGCLICommand(this.deviceName, postData)
+          .pipe(take(1)).subscribe({
+          next: (res: any) => {
+            if (res && res.contents === "") {
+              this._notification.showSuccess('The VPN Resource Group has been deleted successfully.');
+              this.getVPNResourceGroups();
+            }
+          },
+          error: error => {
+            this._notification.showError(`Error: ${error?.message}`);
+            this._cdRef.detectChanges();
+          }
+        });
+      }
+    })
+  }
+
+  syncVPNResourceGroup(_group: any) {
+    console.log('syncGroup', _group);
+    this.dialogConfig.data = {
+      serviceName: this.serviceName,
+      deviceName: this.deviceName,
+      base_vpn: _group
+    }
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(SyncVPNResourceGroupDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getVPNResourceGroups();
+      }
+    })
+  }
+}
+
+@Component({
+  selector: 'create-vpn-resource-group',
+  templateUrl: './create-vpn-resource-group.html',
+  imports: [SharedModule]
+})
+export class CreateVPNResourceGroupDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<CreateVPNResourceGroupDialog>);
+
+  configForm!: FormGroup;
+
+  constructor(
+    private _fB: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._fB.group({
+      group_name: ['', Validators.required],
+      role_name: [[]],
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let payload: any = {
+      'cmd': `vpn resource group "${this.configForm.value.group_name}"`,
+      'vsite_name': this.data?.serviceName
+    }
+    this._vpn.executeAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (res: any) => {
+        if (res && res.contents == "") {
+          this.updateRoles();
+        } else {
+          this._notification.showError(`Failed to create the VPN Resource Group.`);
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  updateRoles() {
+    let roles: any = this.configForm.value.role_name;
+    if (roles.length > 0) {
+      let cmd_list: any = ["sw \"" + this.data?.serviceName + "\""]
+      let cmd_dict: any = {};
+      roles.forEach((_role: any) => {
+        cmd_list.push('role resource vpnresourcegroup "' + _role + '" "' + this.configForm.value.group_name + '"');
+        cmd_dict['role resource vpnresourcegroup "' + _role + '" "' + this.configForm.value.group_name + '"'] = _role;
+      })
+      let payload: any = {'config': cmd_list.join('\n')};
+      let result_list: any = [];
+      this._vpn.batchAGCLICommand(this.data?.deviceName, payload)
+        .pipe(take(1)).subscribe({
+        next: (res: any) => {
+          if (res && res.contents === "") {
+            this._notification.showSuccess(`The VPN Resource Group has been created successfully.`);
+            this.dialogRef.close(true);
+          } else {
+            let result_str: any = "Error occurred during operation";
+            result_list.push({
+              "type": "success",
+              "message": 'Add VPN resource group successfully'
+            })
+            let res_list: any = res?.contents.split('\n');
+            let res_dict: any = {};
+            res_list.forEach((res_str: any) => {
+              if (!res_str) {
+                return;
+              }
+              let tmp_list = res_str.split(':"');
+              if (tmp_list[1].indexOf("^") != -1) {
+                res_dict[tmp_list[0]] = 'Parameter error';
+              } else {
+                let tmp_str = tmp_list[1].replace(/^\"|\"$/g, '');
+                tmp_str = tmp_str.replace(/\\/g, '');
+                res_dict[tmp_list[0]] = tmp_str;
+              }
+            })
+            cmd_list.forEach((cmd_str: any) => {
+              if (!cmd_str || cmd_str == "sw \"" + this.data?.serviceName + "\"") {
+                return;
+              }
+              if (res_dict[cmd_str]) {
+                result_list.push({
+                  "type": "error",
+                  "message": `Assign the vpn resource group for role "${[cmd_dict[cmd_str]]}" failed : ${res_dict[cmd_str]}`
+                })
+              } else {
+                result_list.push({
+                  "type": "success",
+                  "message": `Assign the vpn resource group for role "${[cmd_dict[cmd_str]]}" successfully`
+                })
+              }
+            })
+            res_list.forEach((item: any) => {
+              if (item?.type === "success") {
+                this._notification.showSuccess(item?.message);
+              } else {
+                this._notification.showError(item?.message);
+              }
+            })
+          }
+        },
+        error: error => {
+          this._notification.showError(`Error: ${error?.message}`);
+        }
+      });
+    } else {
+      this._notification.showSuccess(`The VPN Resource Group has been created successfully.`);
+      this.dialogRef.close(true);
+    }
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
+
+@Component({
+  selector: 'update-vpn-resource-group',
+  templateUrl: './update-vpn-resource-group.html',
+  imports: [SharedModule]
+})
+export class UpdateVPNResourceGroupDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<UpdateVPNResourceGroupDialog>);
+
+  configForm!: FormGroup;
+
+  constructor(
+    private _fB: FormBuilder,
+    private _vpn: VpnService,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._fB.group({
+      group_name: [{value: '', disabled: true}, Validators.required],
+      role_name: [[]],
+    });
+    this.configForm.patchValue({
+      group_name: this.data?.group_name,
+      role_name: this.data?.role_name,
+    })
+  }
+
+  onSubmit() {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let cmd_list: any = ["sw \"" + this.data?.serviceName + "\""];
+    let cmd_dict: any = {};
+    let roles: any = this.configForm.value?.role_name;
+    this.data?.role_name.forEach((_role: any) => {
+      if (roles.indexOf(_role?.role_name) == -1) {
+        cmd_list.push('no role resource vpnresourcegroup "' + _role?.role_name + '" "' + this.data?.group_name + '"')
+        cmd_dict['no role resource vpnresourcegroup "' + _role?.role_name + '" "' + this.data?.group_name + '"'] = _role?.role_name;
+      }
+    })
+    roles.forEach((_role: any) => {
+      if (this.data?.role_name.indexOf(_role?.role_name) == -1) {
+        cmd_list.push('role resource vpnresourcegroup "' + _role?.role_name + '" "' + this.data?.group_name + '"');
+        cmd_dict['role resource vpnresourcegroup "' + _role?.role_name + '" "' + this.data?.group_name + '"'] = _role?.role_name;
+      }
+    })
+    let payload: any = {'config': cmd_list.join('\n')}
+    this._vpn.batchAGCLICommand(this.data?.deviceName, payload)
+      .pipe(take(1)).subscribe({
+      next: (res: any) => {
+        if (res && res.contents === "Successful") {
+          this._notification.showSuccess(`The VPN Resource Group has been updated successfully.`);
+          this.dialogRef.close(true);
+        } else {
+          let result_str: any = "Error occurred during operation";
+          let res_list: any = res.data['contents'].split('\n');
+          let res_dict: any = {};
+          let result_list: any = [];
+          res_list.forEach((res_str: any) => {
+            if (!res_str) {
+              return;
+            }
+            let tmp_list = res_str.split(':"');
+            if (tmp_list[1].indexOf("^") != -1) {
+              res_dict[tmp_list[0]] = 'Parameter error';
+            } else {
+              let tmp_str = tmp_list[1].replace(/^\"|\"$/g, '');
+              tmp_str = tmp_str.replace(/\\/g, '');
+              res_dict[tmp_list[0]] = tmp_str;
+            }
+
+          })
+
+          cmd_list.forEach((cmd_str: any) => {
+            if (!cmd_str || cmd_str == "sw \"" + this.data?.serviceName + "\"") {
+              return;
+            }
+            if (res_dict[cmd_str]) {
+              if (cmd_str.indexOf('no role resource') != -1) {
+                result_list.push({
+                  "type": "error",
+                  "message": `Unassign the vpn resource group for role "${[cmd_dict[cmd_str]]}" failed: ${res_dict[cmd_str]}`
+                })
+              } else {
+                result_list.push({
+                  "type": "error",
+                  "message": `Assign the vpn resource group for role "${[cmd_dict[cmd_str]]}" failed: ${res_dict[cmd_str]}`
+                })
+              }
+
+            } else {
+              if (cmd_str.indexOf('no role resource') != -1) {
+                result_list.push({
+                  "type": "success",
+                  "message": `Unassign the vpn resource group for role "${[cmd_dict[cmd_str]]}" successfully`
+                })
+              } else {
+                result_list.push({
+                  "type": "success",
+                  "message": `Assign the vpn resource group for role "${[cmd_dict[cmd_str]]}" successfully`
+                })
+              }
+            }
+          })
+          res_list.forEach((item: any) => {
+            if (item?.type === "success") {
+              this._notification.showSuccess(item?.message);
+            } else {
+              this._notification.showError(item?.message);
+            }
+          })
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
+
+@Component({
+  selector: 'sync-vpn-resource-group',
+  templateUrl: './sync-vpn-resource-group.html',
+  imports: [SharedModule]
+})
+export class SyncVPNResourceGroupDialog implements OnInit, OnDestroy {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<SyncVPNResourceGroupDialog>);
+
+  configForm!: FormGroup;
+  serviceOptions: any = [];
+
+  constructor(
+    private _vpn: VpnService,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _formBuilder: FormBuilder,
+    private _cdRef: ChangeDetectorRef,
+  ) {
+  }
+
+  ngOnInit() {
+    this.configForm = this._formBuilder.group({
+      services: ['', Validators.required],
+    })
+    setTimeout(() => {
+      this.getAMPDevices();
+    })
+  }
+
+  getAMPDevices() {
+    this.serviceOptions = [];
+    this._device.getAMPDevicesList()
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.length > 0) {
+          result.forEach((_device: any) => {
+            if (_device.type.toLowerCase() === 'ag' || _device.type.toLowerCase() === 'vxag') {
+              this.getVpnServices(_device?.name, _device?.device_group);
+            }
+          })
+        }
+        this._cdRef.detectChanges();
+      },
+      error: (error: any) => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  getVpnServices(aGName: string, device_group: string): void {
+    let payload = {cmd: 'show virtual site config'}
+    this._vpn.getVPNServices(aGName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          let vsites = this.processVirtualSiteData(result?.contents);
+          vsites.forEach((vsite: any) => {
+            vsite.device_name = aGName;
+            vsite.device_group = device_group;
+            if (vsite.deviceName !== this.data?.deviceName && vsite?.name !== this.data?.serviceName) {
+              this.serviceOptions.push(vsite);
+            }
+          })
+        }
+        this._cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this._cdRef.detectChanges();
+      }
+    })
+  }
+
+  processVirtualSiteData(confList: any[]) {
+    const vsiteDict: { [key: string]: any } = {};
+    const result: any[] = [];
+
+    confList.forEach((data: any) => {
+      const vsiteName = data.vsite_name;
+
+      if (data.cmdid === 'virtual site name') {
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].type = data.vsite_type || '';
+          vsiteDict[vsiteName].vsite_desc = data.vsite_desc || '';
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: data.vsite_type || '',
+            vsite_desc: data.vsite_desc || '',
+            ip: [],
+            ip_str: '',
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site ip') {
+        const ip = data.ip ? data.ip.slice(3, -1) : '';
+        const port = data.port || '';
+        const ipStr = `${ip}:${port}`;
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].ip.push(ipStr);
+          vsiteDict[vsiteName].ip_str += `${ipStr}, `;
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: '',
+            vsite_desc: '',
+            ip: [ipStr],
+            ip_str: `${ipStr}, `,
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site domain') {
+        const domainName = data.domain_name || '';
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].domain.push(domainName);
+          vsiteDict[vsiteName].domain_str += `${domainName}, `;
+        } else {
+          const newVsite: any = {
+            name: vsiteName,
+            type: '',
+            vsite_desc: '',
+            ip: [],
+            ip_str: '',
+            domain: [domainName],
+            domain_str: `${domainName}, `
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      }
+    });
+    result.forEach(vsite => {
+      if (vsite.ip_str.endsWith(', ')) {
+        vsite.ip_str = vsite.ip_str.slice(0, -2);
+      }
+      if (vsite.domain_str.endsWith(', ')) {
+        vsite.domain_str = vsite.domain_str.slice(0, -2);
+      }
+    });
+
+    return result;
+  }
+
+  ngOnDestroy() {
+  }
+
+  onSubmit(): void {
+    if (this.configForm.invalid) {
+      console.log(this.configForm.value);
+      this.configForm.markAllAsTouched();
+      return;
+    }
+    let services: any = this.configForm.value?.services;
+    let base_vpn: any = this.data?.base_vpn?.group_name;
+    let result: any = [
+      `clear vpn resource groupitem appname "${base_vpn}"`,
+      `clear vpn resource groupitem network "${base_vpn}"`,
+      `clear vpn resource groupexcludeditem appname "${base_vpn}"`,
+      `clear vpn resource groupexcludeditem network "${base_vpn}"`,
+      `vpn resource group "${base_vpn}"`,
+    ];
+    let all_dict: any = {}
+    let all_result_list: any = [];
+    let post_data: any = {'cmd': 'show vpn config', 'vsite_name': this.data?.serviceName};
+    if (services.length > 0 && !(services.length === 1 && services[0] === '')) {
+      this._vpn.executeAGCLICommand(this.data?.deviceName, post_data)
+        .pipe(take(1)).subscribe({
+        next: (res: any) => {
+          if (res && res.contents) {
+            let conf_list: any = res['contents'];
+            conf_list.forEach((item: any) => {
+              if (item?.cmdid === 'vpn resource groupitem appname') {
+                if (item?.resourcegroup === base_vpn) {
+                  result.push('vpn resource groupitem appname "' + base_vpn + '" "' + item?.appname
+                    + '" "' + item?.exename + '" "' + item?.hash + '"');
+                  all_dict['vpn resource groupitem appname "' + base_vpn + '" "' + item?.appname
+                  + '" "' + item?.exename + '" "' + item?.hash + '"'] = item?.appname;
+                }
+              }
+              if (item?.cmdid === 'vpn resource groupitem network') {
+                if (item?.resourcegroup == base_vpn) {
+                  result.push('vpn resource groupitem network "' + base_vpn + '" "' + item?.netresource
+                    + '" ' + item?.type);
+                  all_dict['vpn resource groupitem network "' + base_vpn + '" "' + item?.netresource
+                  + '" ' + item?.type] = item?.netresource;
+                }
+              }
+              if (item?.cmdid === 'vpn resource groupexcludeditem appname') {
+                if (item?.resourcegroup == base_vpn) {
+                  result.push('vpn resource groupexcludeditem appname "' + base_vpn + '" "' + item?.appname
+                    + '" "' + item?.exename + '"');
+                  all_dict['vpn resource groupexcludeditem appname "' + base_vpn + '" "' + item?.appname
+                  + '" "' + item?.exename + '"'] = item?.appname;
+                }
+              }
+              if (item?.cmdid === 'vpn resource groupexcludeditem network') {
+                if (item?.resourcegroup == base_vpn) {
+                  result.push('vpn resource groupexcludeditem network "' + base_vpn + '" "' + item?.netresource
+                    + '" ' + item?.type);
+                  all_dict['vpn resource groupexcludeditem network "' + base_vpn + '" "' + item?.netresource
+                  + '" ' + item?.type] = item?.netresource;
+                }
+              }
+            });
+            let role_post_data: any = {
+              'cmd': 'show role resource "" vpnresourcegroup',
+              'vsite_name': this.data?.serviceName
+            };
+            this._vpn.executeAGCLICommand(this.data?.deviceName, role_post_data)
+              .pipe(take(1)).subscribe({
+              next: (resp: any) => {
+                if (resp?.contents !== '') {
+                  conf_list = resp?.contents;
+                  conf_list.forEach((item: any) => {
+                    if (item?.cmdid == 'role resource vpnresourcegroup') {
+                      if (item?.group_name == base_vpn) {
+                        result.push('role resource vpnresourcegroup "' + item?.role_name + '" "' + base_vpn + '"');
+                        all_dict['role resource vpnresourcegroup "' + item?.role_name + '" "' + base_vpn + '"'] = item?.role_name;
+                      }
+                    }
+                  });
+                }
+                let cmd_list: any = [];
+                let cmd_dict: any = {};
+
+                services.forEach((item: any) => {
+                  let cmd_str = 'change to "' + item?.name + '"\nsw "' + item?.name + '"\n' + result.join('\n');
+                  if (cmd_dict[item?.device_name]) {
+                    cmd_dict[item?.device_name]['cmd_str'] += '\n' + cmd_str;
+                    cmd_dict[item?.device_name]['vsite_list'].push(item?.name);
+                  } else {
+                    cmd_dict[item?.device_name] = {
+                      'cmd_str': cmd_str,
+                      'device_name': item?.device_name,
+                      'vsite_list': [item?.name]
+                    }
+                    cmd_list.push(cmd_dict[item?.device_name]);
+                  }
+                });
+                let process_done: any = [];
+                let all_has_error: boolean = false;
+                cmd_list.forEach((cmd_obj: any) => {
+                  post_data = {'config': cmd_obj?.cmd_str};
+                  this._vpn.batchAGCLICommand(cmd_obj?.device_name, post_data)
+                    .pipe(take(1)).subscribe({
+                    next: (response: any) => {
+                      process_done.push(true);
+                      if (response?.contents !== 'Successful') {
+                        let res_list: any = response?.contents.split('\n');
+                        let result_list: any = [];
+                        let res_dict: any = {};
+                        let curr_vs: any = "";
+                        let has_error: boolean = false;
+                        res_list.forEach((res_str: any) => {
+                          if (!res_str) {
+                            return;
+                          }
+                          if (res_str.indexOf("clear vpn resource group") != -1) {
+                            return;
+                          }
+                          if (res_str.indexOf("Resource group " + base_vpn + " has existed") != -1) {
+                            return;
+                          }
+                          let tmp_cmd: any = res_str.split(':"');
+                          if (res_str.indexOf("change to") != -1) {
+                            curr_vs = tmp_cmd[0].replace(/^change to \"|\"$/g, '');
+                            if (!(curr_vs in res_dict)) {
+                              res_dict[curr_vs] = {};
+                            }
+                            return;
+                          }
+                          if (curr_vs) {
+                            let tmp_str = tmp_cmd[1].replace(/^\"|\"$/g, '');
+                            tmp_str = tmp_str.replace(/\\/g, '');
+                            res_dict[curr_vs][tmp_cmd[0]] = tmp_str;
+                            has_error = true;
+                            all_has_error = true;
+                          }
+                        });
+                        if (!has_error) {
+                          all_result_list.push({
+                            type: "success",
+                            message: `Sync VPN resource group to device "${[cmd_obj['device_name']]}" successfully`
+                          });
+                        } else {
+                          cmd_obj?.vsite_list.forEach((vs: any) => {
+                            if (res_dict?.vs === null || typeof res_dict.vs === 'undefined') {
+                              all_result_list.push({
+                                type: "success",
+                                message: `Sync VPN resource group to device "${cmd_obj['device_name']}" virtual site "${vs}" successfully.`
+                              });
+                            } else {
+                              all_result_list.push({
+                                type: "normal",
+                                message: `Do Syncing on device "${cmd_obj['device_name']}" virtual site "${vs}"`
+                              });
+                            }
+                            result.forEach((cmd: any) => {
+                              if (cmd.indexOf("clear vpn resource group") != -1) {
+                              }
+                              if (cmd.indexOf("vpn resource group \"") != -1) {
+                                if (res_dict[vs][cmd]) {
+                                  all_result_list.push({
+                                    type: "error",
+                                    message: `Create VPN resource group failed : ${res_dict[vs][cmd]}`
+                                  });
+                                } else {
+                                  all_result_list.push({
+                                    type: "success",
+                                    message: `Create VPN resource group successfully`
+                                  });
+                                }
+                              }
+                              if (cmd.indexOf("vpn resource groupitem appname") != -1) {
+                                if (res_dict[vs][cmd]) {
+                                  all_result_list.push({
+                                    type: "error",
+                                    message: `Create appname VPN resource groupitem "${all_dict[cmd]}" failed : ${res_dict[vs][cmd]}`
+                                  });
+                                } else {
+                                  all_result_list.push({
+                                    type: "success",
+                                    message: `Create appname VPN resource groupitem "${all_dict[cmd]}" successfully`
+                                  });
+                                }
+                              }
+                              if (cmd.indexOf("vpn resource groupitem network") != -1) {
+                                if (res_dict[vs][cmd]) {
+                                  all_result_list.push({
+                                    type: "error",
+                                    message: `Create network VPN resource groupitem "${all_dict[cmd]}" failed : ${res_dict[vs][cmd]}`
+                                  });
+                                } else {
+                                  all_result_list.push({
+                                    type: "success",
+                                    message: `Create network VPN resource groupitem "${all_dict[cmd]}" successfully`
+                                  });
+                                }
+                              }
+                              if (cmd.indexOf("vpn resource groupexcludeditem appname") != -1) {
+                                if (res_dict[vs][cmd]) {
+                                  all_result_list.push({
+                                    type: "error",
+                                    message: `Create appname VPN resource groupexcludeditem "${all_dict[cmd]}" failed : ${res_dict[vs][cmd]}`
+                                  });
+                                } else {
+                                  all_result_list.push({
+                                    "type": "success",
+                                    "message": `Create appname VPN resource groupexcludeditem "${all_dict[cmd]}" successfully`
+                                  });
+                                }
+                              }
+                              if (cmd.indexOf("vpn resource groupexcludeditem network") != -1) {
+                                if (res_dict[vs][cmd]) {
+                                  all_result_list.push({
+                                    type: "error",
+                                    message: `Create network VPN resource groupexcludeditem "${all_dict[cmd]}" failed : ${res_dict[vs][cmd]}`
+                                  });
+                                } else {
+                                  all_result_list.push({
+                                    type: "success",
+                                    message: `Create network VPN resource groupexcludeditem "${all_dict[cmd]}" successfully`
+                                  });
+                                }
+                              }
+                              if (cmd.indexOf("role resource vpnresourcegroup") != -1) {
+                                if (res_dict[vs][cmd]) {
+                                  all_result_list.push({
+                                    type: "error",
+                                    message: `Assign the vpn resource group for role "$\{all_dict[cmd]}" failed : ${res_dict[vs][cmd]}`
+                                  });
+                                } else {
+                                  all_result_list.push({
+                                    type: "success",
+                                    message: `Assign the vpn resource group for role "${all_dict[cmd]}" successfully`
+                                  });
+                                }
+                              }
+                            })
+                          })
+                        }
+                      }
+                      let _success: any = [];
+                      let _failure: any = [];
+                      all_result_list.forEach((msg: any) => {
+                        if (msg?.type === 'normal' || msg?.type === 'success') {
+                          _success.push(msg?.message);
+                        } else {
+                          _failure.push(msg?.message);
+                        }
+                      })
+                      if (_success.length > 0) {
+                        this._notification.showSuccess(_success);
+                      }
+                      if (_failure.length > 0) {
+                        console.log(_failure);
+                        this._notification.showError(_failure);
+                      }
+                      this.dialogRef.close(true);
+                      // ToDo: Update this logic to display the success/failure msgs in bulk.
+                      // if (process_done.length == cmd_list.length) {
+                      //   if (all_has_error) {
+                      //     this._notification.showError(`Error: Error occurred during operation`);
+                      //   } else {
+                      //     this._notification.showSuccess(`The VPN Group resources has been synced successfully.`);
+                      //     this.dialogRef.close(true);
+                      //   }
+                      // }
+
+                    },
+                    error: (error: any) => {
+                      this._notification.showError(`Error: ${error}`);
+                    }
+                  });
+                })
+              },
+              error: (error: any) => {
+                this._notification.showError(`Error: ${error}`);
+              }
+            });
+          } else {
+            this._notification.showError(`Error: ${res}`);
+          }
+        },
+        error: error => {
+          this._notification.showError(`Error: ${error?.message}`);
+        },
+      });
+    }
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.html	(revision 2679)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.html	(working copy)
@@ -1 +1,49 @@
-<p>vpn-management works!</p>
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>VPN Management</mat-card-title>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="dataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="serial">
+      <th mat-header-cell *matHeaderCellDef> No.</th>
+      <td mat-cell *matCellDef="let element; let i = index;"> {{ getGlobalSerial(i) }}</td>
+    </ng-container>
+    <ng-container matColumnDef="serviceName">
+      <th mat-header-cell *matHeaderCellDef> Service Name</th>
+      <td mat-cell *matCellDef="let element">
+        <a class="details-page-link" (click)="goToDetails(element)">{{element?.name}}</a>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="deviceName">
+      <th mat-header-cell *matHeaderCellDef> Device Name</th>
+      <td mat-cell *matCellDef="let element">{{element?.device_name}}</td>
+    </ng-container>
+    <ng-container matColumnDef="deviceGroup">
+      <th mat-header-cell *matHeaderCellDef> Device Group</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.device_group }}</td>
+    </ng-container>
+    <ng-container matColumnDef="ip">
+      <th mat-header-cell *matHeaderCellDef> IP Address</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.ip_str }}</td>
+    </ng-container>
+    <ng-container matColumnDef="domain">
+      <th mat-header-cell *matHeaderCellDef> Domain</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.domain_str }}</td>
+    </ng-container>
+    <ng-container matColumnDef="type">
+      <th mat-header-cell *matHeaderCellDef> Type</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.type }}</td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="vpnColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: vpnColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="10">No results found.</td>
+    </tr>
+  </table>
+  <mat-paginator
+    [pageSizeOptions]="[10, 15, 20, 25]"
+    [length]="totalRecords"
+    showFirstLastButtons
+  ></mat-paginator>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.scss	(revision 2679)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.scss	(working copy)
@@ -0,0 +1,21 @@
+.page-card-1 {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+
+  mat-card-header {
+    color: #1170cf;
+  }
+}
+
+mat-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 10px;
+}
+
+mat-card-title {
+  font-size: medium;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.ts	(revision 2679)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/vpn-management/vpn-management.ts	(working copy)
@@ -1,11 +1,180 @@
-import { Component } from '@angular/core';
+import {Component, inject, ViewChild} from '@angular/core';
+import {MatTableDataSource} from '@angular/material/table';
+import {SharedModule} from '../../shared/shared-module';
+import {MatDialog, MatDialogConfig} from '@angular/material/dialog';
+import {MatPaginator} from '@angular/material/paginator';
+import {VpnService} from '../../services/vpn-service';
+import {take} from 'rxjs/operators';
+import {NotificationService} from '../../services/notification';
+import {DeviceService} from '../../services/device-service';
+import {Router} from '@angular/router';
 
 @Component({
   selector: 'app-vpn-management',
-  imports: [],
+  imports: [SharedModule],
   templateUrl: './vpn-management.html',
   styleUrl: './vpn-management.scss'
 })
 export class VpnManagement {
 
+  totalRecords: number = 0;
+  vpnColumns: string[] = ['serial', 'serviceName', 'deviceName', 'deviceGroup', 'ip', 'domain', 'type'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  selectedVSite: any = null;
+
+  constructor(
+    private _vpn: VpnService,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _router: Router
+  ) {
+    // this.getVpnServices();
+    this.dataSource.paginator = this.paginator;
+    this.getAMPDevices();
+  }
+
+  getAMPDevices() {
+    this.dataSource.data = [];
+    this.totalRecords = 0;
+    this._device.getAMPDevicesList()
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.length > 0) {
+          result.forEach((_device: any) => {
+            if (_device.type.toLowerCase() === 'ag' || _device.type.toLowerCase() === 'vxag') {
+              this.getVpnServices(_device?.name, _device?.device_group);
+            }
+          })
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    })
+  }
+
+  getVpnServices(aGName: string, device_group: string): void {
+    let payload = {cmd: 'show virtual site config'}
+    this._vpn.getVPNServices(aGName, payload)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.contents && result.contents.length > 0) {
+          let vsites = this.processVirtualSiteData(result?.contents);
+          vsites.forEach((vsite: any) => {
+            vsite.device_name = aGName;
+            vsite.device_group = device_group;
+            this.dataSource.data.push(vsite);
+          })
+          this.dataSource.paginator = this.paginator;
+          this.totalRecords = this.dataSource.data.length;
+        }
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    })
+  }
+
+  processVirtualSiteData(confList: any[]) {
+    const vsiteDict: { [key: string]: any } = {};
+    const result: any[] = [];
+
+    confList.forEach((data: any) => {
+      const vsiteName = data.vsite_name;
+
+      if (data.cmdid === 'virtual site name') {
+        if (vsiteDict[vsiteName]) {
+          // Update existing entry
+          vsiteDict[vsiteName].type = data.vsite_type || ''; // Default to empty string if undefined
+          vsiteDict[vsiteName].vsite_desc = data.vsite_desc || ''; // Default to empty string if undefined
+        } else {
+          // Create new entry
+          const newVsite: any = {
+            name: vsiteName,
+            type: data.vsite_type || '',
+            vsite_desc: data.vsite_desc || '',
+            ip: [],
+            ip_str: '',
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site ip') {
+        const ip = data.ip ? data.ip.slice(3, -1) : ''; // Handle potential undefined ip
+        const port = data.port || ''; // Handle potential undefined port
+        const ipStr = `${ip}:${port}`;
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].ip.push(ipStr);
+          vsiteDict[vsiteName].ip_str += `${ipStr}, `;
+        } else {
+          // Create new entry if IP record comes before 'virtual site name'
+          const newVsite: any = {
+            name: vsiteName,
+            type: '', // Type and desc unknown at this point, will be filled if 'virtual site name' appears later
+            vsite_desc: '',
+            ip: [ipStr],
+            ip_str: `${ipStr}, `,
+            domain: [],
+            domain_str: ''
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      } else if (data.cmdid === 'virtual site domain') {
+        const domainName = data.domain_name || ''; // Handle potential undefined domain_name
+
+        if (vsiteDict[vsiteName]) {
+          vsiteDict[vsiteName].domain.push(domainName);
+          vsiteDict[vsiteName].domain_str += `${domainName}, `;
+        } else {
+          // Create new entry if Domain record comes before 'virtual site name'
+          const newVsite: any = {
+            name: vsiteName,
+            type: '', // Type and desc unknown at this point
+            vsite_desc: '',
+            ip: [],
+            ip_str: '',
+            domain: [domainName],
+            domain_str: `${domainName}, `
+          };
+          vsiteDict[vsiteName] = newVsite;
+          result.push(newVsite);
+        }
+      }
+    });
+
+    // Clean up trailing commas in ip_str and domain_str
+    result.forEach(vsite => {
+      if (vsite.ip_str.endsWith(', ')) {
+        vsite.ip_str = vsite.ip_str.slice(0, -2);
+      }
+      if (vsite.domain_str.endsWith(', ')) {
+        vsite.domain_str = vsite.domain_str.slice(0, -2);
+      }
+    });
+
+    return result;
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+  goToDetails(vsite: any) {
+    this.selectedVSite = vsite;
+    this._router.navigate(['/vpn-management/details', vsite?.device_name, vsite?.name], {
+      state: {serviceDetails: this.selectedVSite}
+    });
+  }
 }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/global-serial-pipe.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/global-serial-pipe.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/global-serial-pipe.spec.ts	(working copy)
@@ -0,0 +1,8 @@
+import { GlobalSerialPipe } from './global-serial-pipe';
+
+describe('GlobalSerialPipe', () => {
+  it('create an instance', () => {
+    const pipe = new GlobalSerialPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/global-serial-pipe.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/global-serial-pipe.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/global-serial-pipe.ts	(working copy)
@@ -0,0 +1,16 @@
+import {Pipe, PipeTransform} from '@angular/core';
+import {MatPaginator} from '@angular/material/paginator';
+
+@Pipe({
+  name: 'globalSerial'
+})
+export class GlobalSerialPipe implements PipeTransform {
+
+  transform(index: number, paginator?: MatPaginator): number {
+    if (paginator) {
+      return paginator.pageIndex * paginator.pageSize + index + 1;
+    }
+    return index + 1;
+  }
+
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/vpn-service.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/vpn-service.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/vpn-service.spec.ts	(working copy)
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { VpnService } from './vpn-service';
+
+describe('VpnService', () => {
+  let service: VpnService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(VpnService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/vpn-service.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/vpn-service.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/vpn-service.ts	(working copy)
@@ -0,0 +1,60 @@
+import {Injectable} from '@angular/core';
+import {URLS} from '../constants/api_urls';
+import {HttpService} from './http';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class VpnService {
+
+  constructor(private http: HttpService) {
+  }
+
+  getVPNServices(aGName: string, rawPayload: any) {
+    return this.http.post(URLS.GET_VPN_SERVICES_LIST_URL, rawPayload, {
+        csrf: true,
+        isFormData: true,
+        csrfInFormData: true
+      }, [
+        {name: 'Cm-Data', value: aGName},
+        {name: 'Cm-Type', value: 'device'}
+      ]
+    );
+  }
+
+  executeAGCLICommand(aGName: string, rawPayload: any) {
+    return this.http.post(URLS.GET_VPN_SERVICES_LIST_URL, rawPayload, {
+        csrf: true,
+        isFormData: true,
+        csrfInFormData: true
+      }, [
+        {name: 'Cm-Data', value: aGName},
+        {name: 'Cm-Type', value: 'device'}
+      ]
+    );
+  }
+
+  getHardwareIds(aGName: string, rawPayload: any) {
+    return this.http.post(URLS.GET_VPN_SERVICES_LIST_URL, rawPayload, {
+        csrf: true,
+        isFormData: true,
+        csrfInFormData: true
+      }, [
+        {name: 'Cm-Data', value: aGName},
+        {name: 'Cm-Type', value: 'device'}
+      ]
+    );
+  }
+
+  batchAGCLICommand(aGName: string, rawPayload: any) {
+    return this.http.post(URLS.BATCH_VPN_CLI_URL, rawPayload, {
+        csrf: true,
+        isFormData: true,
+        csrfInFormData: true
+      }, [
+        {name: 'Cm-Data', value: aGName},
+        {name: 'Cm-Type', value: 'device'}
+      ]
+    );
+  }
+}
