Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/proxy.conf.json
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/proxy.conf.json	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/proxy.conf.json	(working copy)
@@ -3,6 +3,7 @@
     "target": "https://192.168.85.47:8888",
     "secure": false,
     "changeOrigin": true,
-    "pathRewrite": { "^/api": "" }
+    "pathRewrite": { "^/api": "" },
+    "logLevel": "debug"
   }
 }
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 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.routes.ts	(working copy)
@@ -17,6 +17,9 @@
 import {VolumeLicenseOverview} from './components/volume-license-overview/volume-license-overview';
 import {ConfigHub} from './components/config-hub/config-hub';
 import {DeviceBackupFiles} from './components/sub-components/device-backup-files/device-backup-files';
+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';
 
 
 export const routes: Routes = [
@@ -102,6 +105,22 @@
         ]
       },
       {
+        path: 'vpn-management',
+        children: [
+          {
+            path: '',
+            component: VpnManagement,
+            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: '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/upgrade-centre/add-device-build.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/add-device-build.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/add-device-build.html	(working copy)
@@ -0,0 +1,85 @@
+<h2 mat-dialog-title>Add Device Build</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="deviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="app_name" class="form-label">Build Name *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="app_name"
+          formControlName="app_name"
+          matInput
+          placeholder="Build Name"
+          type="text"
+        />
+        @if (deviceForm.get('app_name')?.invalid && deviceForm.get('app_name')?.touched) {
+          <mat-error>
+            @if (deviceForm.get('app_name')?.errors?.['required']) {
+              Build Name is required.
+            } @else if (deviceForm.get('app_name')?.errors) {
+              Invalid name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="build_version" class="form-label">Build Version *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="build_version"
+          formControlName="build_version"
+          matInput
+          placeholder="Standard format like: Rel.XXX.10.7.1.1"
+          type="text"
+        />
+        @if (deviceForm.get('build_version')?.invalid && deviceForm.get('build_version')?.touched) {
+          <mat-error>
+            @if (deviceForm.get('build_version')?.errors?.['required']) {
+              Build Version is required.
+            } @else if (deviceForm.get('build_version')?.errors) {
+              Invalid version format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="md5_value" class="form-label">MD5 Value *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="md5_value"
+          formControlName="md5_value"
+          matInput
+          placeholder="MD5 Value"
+          type="text"
+        />
+        @if (deviceForm.get('md5_value')?.invalid && deviceForm.get('md5_value')?.touched) {
+          <mat-error>
+            @if (deviceForm.get('md5_value')?.errors?.['required']) {
+              MD5 Value is required.
+            } @else if (deviceForm.get('md5_value')?.errors) {
+              Invalid MD5 value 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/upgrade-centre/device-system-update.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/device-system-update.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/device-system-update.html	(working copy)
@@ -0,0 +1,112 @@
+<h2 mat-dialog-title>Device - System Update</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="deviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="device" class="form-label">Device *</label>
+      <mat-radio-group formControlName="device">
+        <mat-radio-button value="device">Device</mat-radio-button>
+        <mat-radio-button value="device_group">Device Group</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    @if (deviceForm.get('device')?.value === 'device') {
+      <div class="form-field-wrapper">
+        <label for="devices" class="form-label">Devices</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <mat-select formControlName="devices" multiple>
+            @for (_option of devices; track _option) {
+              <mat-option [value]="_option.name">{{ _option.name }}</mat-option>
+            }
+          </mat-select>
+          @if (deviceForm.get('devices')?.invalid && deviceForm.get('devices')?.touched) {
+            <mat-error>
+              @if (deviceForm.get('devices')?.errors?.['required']) {
+                Devices is required.
+              } @else {
+                Invalid devices format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+    } @else if (deviceForm.get('device')?.value === 'device_group') {
+      <div class="form-field-wrapper">
+        <label for="device_groups" class="form-label">Device Groups</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <mat-select formControlName="device_groups">
+            @for (_option of groups; track _option) {
+              <mat-option [value]="_option">{{ _option }}</mat-option>
+            }
+          </mat-select>
+          @if (deviceForm.get('device_groups')?.invalid && deviceForm.get('device_groups')?.touched) {
+            <mat-error>
+              @if (deviceForm.get('device_groups')?.errors?.['required']) {
+                Device groups is required.
+              } @else {
+                Invalid device groups format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+    }
+    <div class="form-field-wrapper">
+      <label for="option" class="form-label">Option *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="option">
+          @for (_option of configOptions; track _option) {
+            <mat-option [value]="_option.value">{{ _option.label }}</mat-option>
+          }
+        </mat-select>
+        @if (deviceForm.get('option')?.invalid && deviceForm.get('option')?.touched) {
+          <mat-error>
+            @if (deviceForm.get('option')?.errors?.['required']) {
+              Option is required.
+            } @else {
+              Invalid option format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    @if (deviceForm.get('option')?.value === 'deferred') {
+      <div class="form-field-wrapper">
+        <label for="expire_time" class="form-label">Expire Time *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="expire_time"
+            formControlName="expire_time"
+            matInput
+            placeholder="Expire Time"
+            type="text"
+          />
+          @if (deviceForm.get('expire_time')?.invalid && deviceForm.get('expire_time')?.touched) {
+            <mat-error>
+              @if (deviceForm.get('expire_time')?.errors?.['required']) {
+                Expire time is required.
+              } @else if (deviceForm.get('expire_time')?.errors) {
+                Invalid expire time 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/upgrade-centre/update-device-build.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/update-device-build.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/update-device-build.html	(working copy)
@@ -0,0 +1,43 @@
+<h2 mat-dialog-title>Update Device Build MD5</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="deviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="md5_value" class="form-label">MD5 Value *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="md5_value"
+          formControlName="md5_value"
+          matInput
+          placeholder="MD5 Value"
+          type="text"
+        />
+        @if (deviceForm.get('md5_value')?.invalid && deviceForm.get('md5_value')?.touched) {
+          <mat-error>
+            @if (deviceForm.get('md5_value')?.errors?.['required']) {
+              MD5 Value is required.
+            } @else if (deviceForm.get('md5_value')?.errors) {
+              Invalid MD5 value 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/upgrade-centre/upgrade-centre.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upgrade-centre.html	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upgrade-centre.html	(working copy)
@@ -1 +1,63 @@
-<p>upgrade-centre works!</p>
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>Upgrade Centre</mat-card-title>
+    <div>
+      <button mat-raised-button (click)="addBuild()">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="buildName">
+      <th mat-header-cell *matHeaderCellDef> Build Name</th>
+      <td mat-cell *matCellDef="let element">{{element?.app_name}}</td>
+    </ng-container>
+    <ng-container matColumnDef="version">
+      <th mat-header-cell *matHeaderCellDef> Version</th>
+      <td mat-cell *matCellDef="let element">{{element?.build_version}}</td>
+    </ng-container>
+    <ng-container matColumnDef="md5">
+      <th mat-header-cell *matHeaderCellDef> MD5</th>
+      <td mat-cell *matCellDef="let element">
+        <a class="details-page-link" (click)="updateMD5(element)">
+          {{ element?.md5_value }}
+        </a>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="size">
+      <th mat-header-cell *matHeaderCellDef> File size</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.file_size }}</td>
+    </ng-container>
+    <ng-container matColumnDef="downLink">
+      <th mat-header-cell *matHeaderCellDef> Download Link</th>
+      <td mat-cell *matCellDef="let element"> {{ element?.download_link }}</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', 'circle-up']" size="lg" matTooltip="Upload Build"
+                   (click)="uploadBuild(element)"></fa-icon>
+          <fa-icon [icon]="['fas', 'gears']" size="lg" matTooltip="System Update"
+                   (click)="systemUpdate(element)"></fa-icon>
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Remove Build"
+                   (click)="deleteBuild(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="buildColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: buildColumns;"></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/upgrade-centre/upgrade-centre.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upgrade-centre.scss	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upgrade-centre.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/upgrade-centre/upgrade-centre.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upgrade-centre.ts	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upgrade-centre.ts	(working copy)
@@ -1,11 +1,639 @@
-import { Component } from '@angular/core';
+import {Component, inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../shared/shared-module';
+import {DeviceService} from '../../services/device-service';
+import {NotificationService} from '../../services/notification';
+import {take} from 'rxjs/operators';
+import {MatTableDataSource} from '@angular/material/table';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {MatPaginator} from '@angular/material/paginator';
+import {AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators} from '@angular/forms';
+import {Confirmation} from '../../services/confirmation';
+import {SystemService} from '../../services/system-service';
+import {Subject} from 'rxjs';
 
 @Component({
   selector: 'app-upgrade-centre',
-  imports: [],
+  imports: [SharedModule,],
   templateUrl: './upgrade-centre.html',
   styleUrl: './upgrade-centre.scss'
 })
 export class UpgradeCentre {
+
+  totalRecords: number = 0;
+  buildColumns: string[] = ['serial', 'buildName', 'version', 'md5', 'size', 'downLink', 'action'];
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+
+  builds: any = []
+
+  constructor(
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation,
+  ) {
+    this.dataSource.paginator = this.paginator;
+    this.getAvailableBuilds();
+  }
+
+  getAvailableBuilds() {
+    this.dataSource.data = [];
+    this._device.getAvailableBuilds()
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.length > 0) {
+          this.builds = result;
+          this.dataSource.data = result;
+          this.dataSource.paginator = this.paginator;
+          this.totalRecords = result.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;
+  }
+
+  updateMD5(_build: any) {
+    this.dialogConfig.width = '45%';
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = _build;
+    const dialogRef = this.dialog.open(UpdateDeviceBuildDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getAvailableBuilds();
+      }
+    })
+  }
+
+  addBuild() {
+    this.dialogConfig.width = '45%';
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(AddDeviceBuildDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getAvailableBuilds();
+      }
+    })
+  }
+
+  deleteBuild(_build: any) {
+    let confirmMsg = `Are you sure you want to delete the device build - "${_build?.app_name}"?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete device build - ${_build?.app_name}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No, Keep It',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let rawPayload = new FormData();
+        rawPayload.set('pk', JSON.stringify({
+          app_name: _build?.app_name,
+          build_version: _build?.build_version,
+          id: _build?.id,
+        }));
+        this._device.deleteDeviceBuildUpgradeConfig(rawPayload)
+          .pipe(take(1))
+          .subscribe({
+            next: (result: any) => {
+            },
+            error: (err: any) => {
+              // ToDo: Fix the backend to send proper acknowledgment.
+              this._notification.showSuccess(`${_build?.name} deleted successfully!`);
+              this.getAvailableBuilds()
+            }
+          })
+      } else {
+        this._notification.showSuccess('Delete cancelled.');
+      }
+    })
+  }
+
+  uploadBuild(_build: any) {
+    this.dialogConfig.width = '45%';
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = _build;
+    const dialogRef = this.dialog.open(UploadDeviceBuildDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getAvailableBuilds();
+      }
+    })
+  }
+
+  systemUpdate(_build: any) {
+    this.dialogConfig.width = '45%';
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = _build;
+    const dialogRef = this.dialog.open(DeviceSystemUpdateDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getAvailableBuilds();
+      }
+    })
+  }
+}
+
+@Component({
+  selector: 'add-device-build',
+  templateUrl: './add-device-build.html',
+  imports: [SharedModule]
+})
+export class AddDeviceBuildDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<AddDeviceBuildDialog>);
+
+  deviceForm: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+  ) {
+    this.deviceForm = this._formBuilder.group({
+      md5_value: ['', Validators.required],
+      app_name: ['', Validators.required],
+      build_version: ['', Validators.required],
+    });
+  }
+
+  ngOnInit() {
+
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  onSubmit() {
+    if (this.deviceForm.invalid) {
+      console.log(this.deviceForm.value);
+      this.deviceForm.markAllAsTouched();
+      return;
+    }
+
+    let rawPayload = new FormData();
+    rawPayload.set('post_data', JSON.stringify({
+      md5_value: this.deviceForm.value?.md5_value,
+      app_name: this.deviceForm.value?.app_name,
+      build_version: this.deviceForm.value?.build_version,
+    }));
+    this._device.addDeviceBuildUpgradeConfig(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (!result[0]) {
+              this._notification.showError(`Failed - ${result[1]}`);
+            } else {
+              this._notification.showSuccess(`Device build upgrade configuration added successfully.`);
+              this.dialogRef.close(true);
+            }
+          }
+        },
+        error: (err: any) => {
+          this._notification.showError('Failed to add device build upgrade configuration.');
+        }
+      })
+  }
+}
+
+@Component({
+  selector: 'update-device-build',
+  templateUrl: './update-device-build.html',
+  imports: [SharedModule]
+})
+export class UpdateDeviceBuildDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<UpdateDeviceBuildDialog>);
+
+  deviceForm: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+  ) {
+    this.deviceForm = this._formBuilder.group({
+      md5_value: ['', Validators.required]
+    });
+  }
+
+  ngOnInit() {
+    this.deviceForm.patchValue({
+      md5_value: this.data?.md5_value,
+    })
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  onSubmit() {
+    if (this.deviceForm.invalid) {
+      console.log(this.deviceForm.value);
+      this.deviceForm.markAllAsTouched();
+      return;
+    }
+
+    let rawPayload = new FormData();
+    rawPayload.set('post_data', JSON.stringify({
+      md5_value: this.deviceForm.value?.md5_value,
+    }));
+    this._device.updateDeviceBuildUpgradeConfig(this.data, rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (!result[0]) {
+              this._notification.showError(`Failed - ${result[1]}`);
+            } else {
+              this._notification.showSuccess(`Device build MD5 configuration updated successfully.`);
+              this.dialogRef.close(true);
+            }
+          }
+        },
+        error: (err: any) => {
+          this._notification.showError('Failed to update device build md5 value.');
+        }
+      })
+  }
+}
+
+@Component({
+  selector: 'upload-device-build',
+  templateUrl: './upload-device-build.html',
+  imports: [SharedModule]
+})
+export class UploadDeviceBuildDialog implements OnInit, OnDestroy {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<UploadDeviceBuildDialog>);
+
+  deviceForm: FormGroup;
+  formSubmitted: boolean = false;
+  selectedFile: File | null = null;
+  upload_path: string = '';
+  private destroy$ = new Subject<void>();
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _system: SystemService,
+  ) {
+    this.deviceForm = this._formBuilder.group({
+      using: ['file', Validators.required],
+      upload_file: [''],
+      upload_path: [''],
+      url: [''],
+    });
+  }
+
+  ngOnInit() {
+    this.setConditionalValidators(this.deviceForm.get('using')?.value);
+
+    this.deviceForm.get('using')?.valueChanges
+      .pipe(take(1), take(1))
+      .subscribe(usingValue => {
+        this.formSubmitted = false;
+        this.setConditionalValidators(usingValue);
+      });
+  }
+
+  ngOnDestroy() {
+    this.destroy$.next();
+    this.destroy$.complete();
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
 
+  onFileUploadToServer(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    const file = input.files?.[0];
+
+    if (!file) {
+      this.selectedFile = null;
+      this.upload_path = '';
+      this.deviceForm.get('upload_file')?.setValue(null);
+      this.deviceForm.get('upload_path')?.setValue(null);
+      this.deviceForm.get('upload_file')?.updateValueAndValidity();
+      this.deviceForm.get('upload_path')?.updateValueAndValidity();
+      return;
+    }
+
+    const fileName = file.name;
+    const fileExtension = fileName.split('.').pop()?.toLowerCase();
+    const allowedExtensions = ['array', 'click'];
+
+    if (!fileExtension || !allowedExtensions.includes(fileExtension)) {
+      this._notification.showError('Error: Only .array or .click files are allowed.');
+      input.value = '';
+      this.deviceForm.get('upload_file')?.setValue(null);
+      this.deviceForm.get('upload_file')?.updateValueAndValidity();
+      return;
+    }
+
+    this.selectedFile = file;
+
+    let payload = new FormData();
+    payload.append('file', file, file.name);
+
+    this._system.upload(null, payload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result.files && result.files.length > 0) {
+            this.upload_path = result.files[0].url;
+            this.deviceForm.get('upload_path')?.setValue(this.upload_path);
+            this.deviceForm.get('upload_path')?.updateValueAndValidity();
+          }
+        },
+        error: error => {
+          this._notification.showError(`Error: ${error?.message || 'File upload failed'}`);
+          console.error('File upload error:', error);
+          this.selectedFile = null;
+          this.upload_path = '';
+          this.deviceForm.get('upload_path')?.setValue(null);
+          this.deviceForm.get('upload_path')?.updateValueAndValidity();
+          input.value = '';
+        }
+      });
+  }
+
+  onSubmit(): void {
+    this.formSubmitted = true;
+
+    if (this.deviceForm.invalid) {
+      Object.keys(this.deviceForm.controls).forEach(key => {
+        const control = this.deviceForm.get(key);
+        if (control?.invalid) {
+          console.log(`Control '${key}' is invalid. Errors:`, control.errors);
+        }
+      });
+      this.deviceForm.markAllAsTouched();
+      return;
+    }
+
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'Upload');
+
+    let payload: any = {
+      '__pk_list': [JSON.stringify({
+        app_name: this.data?.app_name,
+        build_version: this.data?.build_version,
+        id: this.data?.id,
+      })],
+    }
+
+    if (this.deviceForm.value?.using === 'url') {
+      payload.using = {URL: this.deviceForm.value.url};
+    }
+
+    if (this.deviceForm.value?.using === 'file') {
+      payload.using = {local: this.selectedFile?.name};
+    }
+
+    rawPayload.set('options', JSON.stringify(payload));
+    this._device.uploadDeviceBuild(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (!result[0]) {
+              this._notification.showError(`Failed - ${result[1]}`);
+            } else {
+              this._notification.showSuccess(`Device build has been uploaded successfully.`);
+              this.dialogRef.close(true);
+            }
+          }
+        },
+        error: (err: any) => {
+          this._notification.showError(`Failed to upload the device build: ${err?.message || 'Server error'}`);
+          console.error('Device build upload error:', err);
+        }
+      });
+  }
+
+  private setConditionalValidators(usingValue: string): void {
+    const uploadFileControl = this.deviceForm.get('upload_file');
+    const uploadPathControl = this.deviceForm.get('upload_path');
+    const urlControl = this.deviceForm.get('url');
+
+    uploadFileControl?.clearValidators();
+    uploadPathControl?.clearValidators();
+    urlControl?.clearValidators();
+
+    uploadFileControl?.reset();
+    uploadPathControl?.reset();
+    urlControl?.reset();
+
+    if (usingValue === 'file') {
+      uploadFileControl?.setValidators(Validators.required);
+      uploadPathControl?.setValidators(Validators.required);
+    } else if (usingValue === 'url') {
+      urlControl?.setValidators([Validators.required, this.urlSuffixValidator()]);
+    }
+
+    this.deviceForm.updateValueAndValidity();
+  }
+
+  private urlSuffixValidator(): ValidatorFn {
+    return (control: AbstractControl): { [key: string]: any } | null => {
+      const url = control.value as string;
+      if (!url) {
+        return null;
+      }
+      const isValid = url.endsWith('.array') || url.endsWith('.click');
+      return isValid ? null : {invalidUrlSuffix: {value: url}};
+    };
+  }
 }
+
+@Component({
+  selector: 'device-system-update',
+  templateUrl: './device-system-update.html',
+  imports: [SharedModule]
+})
+export class DeviceSystemUpdateDialog implements OnInit, OnDestroy {
+
+  readonly data: any = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<DeviceSystemUpdateDialog>);
+
+  deviceForm!: FormGroup;
+  configOptions: any = [
+    {value: 'immediate', label: 'Immediate'},
+    {value: 'deferred', label: 'Deferred'},
+  ]
+  devices: any = [];
+  groups: any = [];
+  deviceGroups: any = [];
+  private destroy$ = new Subject<void>();
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _system: SystemService,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.deviceForm = this._formBuilder.group({
+      device: ['device', Validators.required],
+      devices: [''],
+      device_groups: [''],
+      option: ['immediate', Validators.required],
+      expire_time: [''],
+    }, {validators: this.conditionalValidation()});
+
+    this.deviceForm.get('device')?.valueChanges.subscribe(() => {
+      this.deviceForm.updateValueAndValidity();
+    });
+
+    this.deviceForm.get('option')?.valueChanges.subscribe(() => {
+      this.deviceForm.updateValueAndValidity();
+    });
+    setTimeout(() => {
+      this.getDeviceGroups();
+    })
+  }
+
+  conditionalValidation(): ValidatorFn {
+    return (formGroup: AbstractControl): { [key: string]: any } | null => {
+      const deviceControl = formGroup.get('device');
+      const devicesControl = formGroup.get('devices');
+      const deviceGroupsControl = formGroup.get('device_groups');
+      const optionControl = formGroup.get('option');
+      const expireTimeControl = formGroup.get('expire_time');
+
+      if (!deviceControl || !devicesControl || !deviceGroupsControl || !optionControl || !expireTimeControl) {
+        return null;
+      }
+
+      if (deviceControl.value === 'device') {
+        devicesControl.setValidators(Validators.required);
+        deviceGroupsControl.clearValidators();
+      } else if (deviceControl.value === 'device_group') {
+        deviceGroupsControl.setValidators(Validators.required);
+        devicesControl.clearValidators();
+      } else {
+        devicesControl.clearValidators();
+        deviceGroupsControl.clearValidators();
+      }
+
+      if (optionControl.value === 'deferred') {
+        expireTimeControl.setValidators(Validators.required);
+      } else {
+        expireTimeControl.clearValidators();
+      }
+
+      return null;
+    };
+  }
+
+  getDeviceGroups() {
+    this.devices = [];
+    this.groups = [];
+    this.deviceGroups = []
+    // ToDo: Update with actual RoleId
+    let roleId = "0"
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'FilterRoleDeviceGroups');
+    rawPayload.set('options', JSON.stringify({"role_id": roleId}));
+    this._device.getDeviceGroups(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (result[1] && 'result' in result[1]) {
+              let groups = result[1].result;
+              groups.forEach((group: any) => {
+                if (group?.device_list.length > 0) {
+                  group?.device_list.forEach((device: any) => {
+                    this.devices.push(device);
+                  })
+                  this.groups.push(group?.group_name);
+                  this.deviceGroups.push(group);
+                }
+              })
+              console.log(this.groups, this.devices)
+            }
+          }
+        }, error: (error: { message: string; }) => {
+          console.log(error);
+          this._notification.showError(error.message);
+        }
+      })
+  }
+
+  ngOnDestroy(): void {
+  }
+
+  onSubmit(): void {
+    if (this.deviceForm.invalid) {
+      console.log(this.deviceForm.value);
+      this.deviceForm.markAllAsTouched();
+      return;
+    }
+
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'System_update');
+    let payload: any = {
+      '__pk_list': [JSON.stringify({
+        app_name: this.data?.app_name,
+        build_version: this.data?.build_version,
+        id: this.data?.id,
+      })],
+      backup_db: false,
+      option: this.deviceForm?.value?.option,
+    }
+    if (this.deviceForm.value?.device === 'device') {
+      payload.devices = {device: this.deviceForm.value.devices};
+    }
+    if (this.deviceForm.value?.device === 'device_group') {
+      payload.devices = {device: this.deviceForm.value.device_groups};
+    }
+    if (this.deviceForm.value?.option === 'deferred') {
+      payload.expire_time = this.deviceForm.value.expire_time;
+    }
+    rawPayload.set('options', JSON.stringify(payload));
+    this._device.performDeviceBuildUpdate(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (!result[0]) {
+              this._notification.showError(`Failed - ${result[1]}`);
+            } else {
+              this._notification.showSuccess(`Device build update has been initiated successfully.`);
+              this.dialogRef.close(true);
+            }
+          }
+        },
+        error: (err: any) => {
+          this._notification.showError(`Failed to initiate the device build update: ${err?.message || 'Server error'}`);
+        }
+      });
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upload-device-build.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upload-device-build.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/upgrade-centre/upload-device-build.html	(working copy)
@@ -0,0 +1,66 @@
+<h2 mat-dialog-title>Upload Device Build</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="deviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="using" class="form-label">Using *</label>
+      <mat-radio-group formControlName="using">
+        <mat-radio-button value="file">Local File</mat-radio-button>
+        <mat-radio-button value="url">URL</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    @if (deviceForm.value?.using === 'file') {
+      <div class="form-field-wrapper">
+        <label for="upload_file" class="form-label">Upload Build File *</label>
+        <input formControlName="upload_file" type="file" (change)="onFileUploadToServer($event)">
+        @if (deviceForm.get('upload_file')?.invalid && deviceForm.get('upload_file')?.touched) {
+          <mat-error>
+            @if (deviceForm.get('upload_file')?.errors?.['required']) {
+              Build package is required.
+            } @else {
+              Invalid build package format.
+            }
+          </mat-error>
+        }
+      </div>
+    } @else if (deviceForm.value?.using === 'url') {
+      <div class="form-field-wrapper">
+        <label for="url" class="form-label">URL *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="url"
+            formControlName="url"
+            matInput
+            placeholder="http://"
+            type="text"
+          />
+          @if (deviceForm.get('url')?.invalid && deviceForm.get('url')?.touched) {
+            <mat-error>
+              @if (deviceForm.get('url')?.errors?.['required']) {
+                URL is required.
+              } @else if (deviceForm.get('url')?.errors) {
+                Invalid URL format - Only support file with suffix .array and .click formats.
+              }
+            </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/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 2678)
+++ /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 2678)
+++ /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 2678)
+++ /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/constants/api_urls.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/api_urls.ts	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/api_urls.ts	(working copy)
@@ -124,4 +124,11 @@
   ADD_CONFIG_TEMPLATE_KEY_URL: `${PREFIX}/cm/add_config_template_key`,
   UPDATE_CONFIG_TEMPLATE_KEY_URL: `${PREFIX}/cm/edit_config_template_key`,
   UPDATE_DEVICE_CONFIG_TEMPLATE_DEFAULT_VALUE_URL: `${PREFIX}/cm/edit_config_template_default_value`,
+  GET_VPN_SERVICES_LIST_URL: `${PREFIX}/proxy_req_dev/rest/ag/global/cli_extend`,
+  GET_AVAILABLE_DEVICE_UPGRADE_BUILDS_URL: `${PREFIX}/api/cm/configuration/update/Update/_get_list_data`,
+  ADD_DEVICE_BUILD_UPGRADE_CONFIG_URL: `${PREFIX}/api/cm/configuration/update/Update/_add`,
+  UPDATE_DEVICE_BUILD_UPGRADE_CONFIG_URL: `${PREFIX}/api/cm/configuration/update/Update/_update`,
+  DELETE_DEVICE_BUILD_UPGRADE_CONFIG_URL: `${PREFIX}/api/cm/configuration/update/Update/_delete`,
+  UPLOAD_DEVICE_BUILD_URL: `${PREFIX}/api/cm/configuration/update/Update/_perform`,
+  UPDATE_DEVICE_BUILD_URL: `${PREFIX}/api/cm/configuration/update/Update/_perform`,
 } as const; // Makes properties readonly
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/menu.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/menu.ts	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/menu.ts	(working copy)
@@ -170,27 +170,6 @@
     ]
   },
   {
-    label: 'Volume License',
-    icon: '',
-    expanded: false,
-    children: [
-      {
-        label: 'Licenses',
-        icon: '',
-        routerLink: '/volume-license/license',
-        roles: ['super_admin', 'device_admin', 'common_admin'],
-        permissions: ['vLicenses']
-      },
-      {
-        label: 'Devices',
-        icon: '',
-        routerLink: '/avx/devices',
-        roles: ['super_admin', 'device_admin', 'common_admin'],
-        permissions: ['vDevices']
-      },
-    ]
-  },
-  {
     label: 'VPN Management',
     icon: '',
     routerLink: '/vpn-management',
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/device-service.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/device-service.ts	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/device-service.ts	(working copy)
@@ -229,5 +229,55 @@
 
   getTemplateKeyPairs() {
     return this.http.get(URLS.GET_CONFIG_TEMPLATE_KEY_PAIRS_URL);
+  }
+
+  getAvailableBuilds() {
+    return this.http.get(URLS.GET_AVAILABLE_DEVICE_UPGRADE_BUILDS_URL);
+  }
+
+  addDeviceBuildUpgradeConfig(rawPayload: any) {
+    return this.http.post(URLS.ADD_DEVICE_BUILD_UPGRADE_CONFIG_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  updateDeviceBuildUpgradeConfig(buildInfo: any, rawPayload: any) {
+    let baseURL: string = URLS.UPDATE_DEVICE_BUILD_UPGRADE_CONFIG_URL;
+    baseURL += `/build_version/%22${buildInfo?.build_version}%22/id/${buildInfo?.id}/app_name/%22${buildInfo?.app_name}%22`
+    return this.http.post(baseURL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  deleteDeviceBuildUpgradeConfig(rawPayload: any) {
+    return this.http.post(URLS.DELETE_DEVICE_BUILD_UPGRADE_CONFIG_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
   }
+
+  uploadDeviceBuild(rawPayload: any) {
+    let baseURL: string = URLS.UPLOAD_DEVICE_BUILD_URL;
+    baseURL += `?action=Upload`
+    return this.http.post(baseURL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  performDeviceBuildUpdate(rawPayload: any) {
+    let baseURL: string = URLS.UPDATE_DEVICE_BUILD_URL;
+    baseURL += `?action=System_update`
+    return this.http.post(baseURL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
 }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/http.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/http.ts	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/http.ts	(working copy)
@@ -56,7 +56,7 @@
     return this.http.get<T>(url, this.buildOptions(options));
   }
 
-  post<T>(url: string, data: any, options?: HttpOptions): Observable<T> {
+  post<T>(url: string, data: any, options?: HttpOptions, headers: any = null): Observable<T> {
     const opts = this.buildOptions(options);
 
     if (options?.isForm) {
@@ -64,6 +64,11 @@
     } else if (!options?.isFormData) {
       opts.headers = opts.headers.set('Content-Type', 'application/json');
     }
+    if (headers !== null){
+      headers.forEach((header: any) => {
+        opts.headers = opts.headers.append(header.name, header.value);
+      })
+    }
 
     return this.http.post<T>(url, this.prepareBody(data, options), opts);
   }
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,36 @@
+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'}
+      ]
+    );
+  }
+
+  excuteAGCLICommand(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'}
+      ]
+    );
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/shared/shared-module.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/shared/shared-module.ts	(revision 2678)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/shared/shared-module.ts	(working copy)
@@ -52,6 +52,7 @@
   faClone,
   faClock,
   faWindowRestore,
+  faCircleUp,
 } from '@fortawesome/free-regular-svg-icons';
 import {MatGridListModule} from '@angular/material/grid-list';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -184,6 +185,7 @@
       faClock,
       faWindowRestore,
       faGears,
+      faCircleUp,
     );
   }
 }
