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 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.routes.ts	(working copy)
@@ -11,6 +11,10 @@
 import {Notification} from './components/notification/notification';
 import {AdminTools} from './components/admin-tools/admin-tools';
 import {RoleDetails} from './components/sub-components/role-details/role-details';
+import {
+  VolumeLicenseDevicesOverview
+} from './components/volume-license-devices-overview/volume-license-devices-overview';
+import {VolumeLicenseOverview} from './components/volume-license-overview/volume-license-overview';
 
 
 export const routes: Routes = [
@@ -80,6 +84,22 @@
           },
         ]
       },
+      {
+        path: 'vl',
+        children: [
+          {
+            path: 'licenses',
+            component: VolumeLicenseOverview,
+            data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+          },
+          {
+            path: 'devices',
+            component: VolumeLicenseDevicesOverview,
+            data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+          },
+        ]
+      },
+
       {path: '', redirectTo: 'dashboard', pathMatch: 'full'},
       {path: '**', redirectTo: 'dashboard'}
     ]
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/general-settings/general-settings.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/general-settings/general-settings.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/general-settings/general-settings.html	(working copy)
@@ -1,5 +1,5 @@
 <div class="tab-container">
-  <mat-tab-group animationDuration="0ms">
+  <mat-tab-group animationDuration="0ms" [selectedIndex]="selectedTabIndex" (selectedTabChange)="onTabChange($event)">
     <mat-tab label="Host">
       <ng-template matTabContent>
         <div class="tab-content">
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/general-settings/general-settings.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/general-settings/general-settings.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/general-settings/general-settings.ts	(working copy)
@@ -1,4 +1,4 @@
-import {Component} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
 import {Host} from '../sub-components/host/host';
 import {LogSettings} from '../sub-components/log-settings/log-settings';
 import {SharedModule} from '../../shared/shared-module';
@@ -8,6 +8,8 @@
 import {Network} from '../sub-components/network/network';
 import {AdminAaa} from '../sub-components/admin-aaa/admin-aaa';
 import {SystemBackupRestore} from '../sub-components/system-backup-restore/system-backup-restore';
+import {ActivatedRoute, Router} from '@angular/router';
+import {MatTabChangeEvent} from '@angular/material/tabs';
 
 @Component({
   selector: 'app-general-settings',
@@ -16,7 +18,52 @@
   templateUrl: './general-settings.html',
   styleUrl: './general-settings.scss',
 })
-export class GeneralSettings {
-  constructor() {
+export class GeneralSettings implements OnInit {
+
+  selectedTabIndex: number = 0;
+  private tabNames: string[] = [
+    'Host',
+    'License',
+    'Date & Time',
+    'System Update',
+    'Network',
+    'AAA',
+    'Log Settings',
+    'Backup & Restore',
+  ];
+
+  constructor(private route: ActivatedRoute, private router: Router) {
   }
+
+  ngOnInit(): void {
+    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);
+  }
+
+  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/notification/notification.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/notification/notification.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/notification/notification.html	(working copy)
@@ -1,5 +1,5 @@
 <div class="tab-container">
-  <mat-tab-group animationDuration="0ms">
+  <mat-tab-group animationDuration="0ms" [selectedIndex]="selectedTabIndex" (selectedTabChange)="onTabChange($event)">
     <mat-tab label="Notification Channel">
       <ng-template matTabContent>
         <div class="tab-content">
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/notification/notification.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/notification/notification.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/notification/notification.ts	(working copy)
@@ -1,7 +1,9 @@
-import {Component} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
 import {NotificationChannels} from '../sub-components/notification-channels/notification-channels';
 import {NotificationSettings} from '../sub-components/notification-settings/notification-settings';
 import {SharedModule} from '../../shared/shared-module';
+import {ActivatedRoute, Router} from '@angular/router';
+import {MatTabChangeEvent} from '@angular/material/tabs';
 
 @Component({
   selector: 'app-notification',
@@ -13,6 +15,45 @@
   templateUrl: './notification.html',
   styleUrl: './notification.scss'
 })
-export class Notification {
+export class Notification implements OnInit {
+  selectedTabIndex: number = 0;
+  private tabNames: string[] = [
+    'Notification Channel',
+    'Notification Settings',
+  ];
 
+  constructor(private route: ActivatedRoute, private router: Router) {
+  }
+
+  ngOnInit(): void {
+    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);
+  }
+
+  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/device-groups/device-groups.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-groups/device-groups.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-groups/device-groups.html	(working copy)
@@ -55,4 +55,9 @@
       <td class="mat-cell" colspan="4">No data matching the filter "{{ input.value }}"</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/device-groups/device-groups.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-groups/device-groups.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-groups/device-groups.ts	(working copy)
@@ -1,4 +1,4 @@
-import {Component, inject} from '@angular/core';
+import {Component, inject, ViewChild} from '@angular/core';
 import {take} from 'rxjs/operators';
 import {NotificationService} from '../../../services/notification';
 import {DeviceService} from '../../../services/device-service';
@@ -8,6 +8,7 @@
 import {MatDialog, MatDialogRef} from '@angular/material/dialog';
 import {FormBuilder, FormGroup, Validators} from '@angular/forms';
 import {AlertMsg} from '../../../services/alert-msg';
+import {MatPaginator} from '@angular/material/paginator';
 
 @Component({
   selector: 'app-device-groups',
@@ -21,6 +22,8 @@
   displayedColumns: string[] = ['serial', 'name', 'devices', 'action'];
   dataSource = new MatTableDataSource(this.deviceGroups);
   dialog = inject(MatDialog);
+  totalRecords = 0;
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
 
   constructor(
     private notification: NotificationService,
@@ -29,6 +32,7 @@
     private confirmationService: Confirmation
   ) {
     this.getDeviceGroups();
+    this.dataSource.paginator = this.paginator;
   }
 
   getDeviceGroups(): void {
@@ -46,6 +50,8 @@
             if (result[1] && 'result' in result[1]) {
               this.deviceGroups = result[1].result;
               this.dataSource = new MatTableDataSource(this.deviceGroups);
+              this.dataSource.paginator = this.paginator;
+              this.totalRecords = this.deviceGroups.length;
             }
           }
         },
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.ts	(working copy)
@@ -55,6 +55,7 @@
 
   getDeviceGroups(): void {
     this.devices = [];
+    this.groups = [];
     // ToDo: Update with actual RoleId
     let roleId = "0"
     let rawPayload = new FormData();
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/license/license.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/license/license.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/license/license.ts	(working copy)
@@ -7,8 +7,6 @@
 import {SharedModule} from '../../../shared/shared-module';
 import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
 import {FormBuilder, FormGroup, Validators} from '@angular/forms';
-import {DeviceService} from '../../../services/device-service';
-import {DeviceUpdateLicenseDialog} from '../devices/devices';
 
 export interface KeyValuePair {
   key: string;
@@ -28,6 +26,8 @@
   systemLicense: any = {};
   licenseDataSource = new MatTableDataSource<KeyValuePair>();
   licenseColumns: Array<string> = ['key', 'value'];
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
 
   constructor(
     private systemService: SystemService,
@@ -61,8 +61,6 @@
       })
   }
 
-  dialog = inject(MatDialog);
-  dialogConfig = new MatDialogConfig();
   updateLicense() {
     this.dialogConfig.width = '60%';
     this.dialogConfig.data = {
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/role-details/role-details.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/role-details/role-details.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/role-details/role-details.ts	(working copy)
@@ -179,7 +179,7 @@
     private notification: NotificationService,
   ) {
     this.roleDeviceForm = this.formBuilder.group({
-      groups: ['', Validators.required],
+      groups: [''],
     })
   }
 
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/role-management/role-management.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/role-management/role-management.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/role-management/role-management.ts	(working copy)
@@ -1,4 +1,4 @@
-import {ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
+import {AfterViewInit, ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
 import {SharedModule} from '../../../shared/shared-module';
 import {SystemService} from '../../../services/system-service';
 import {take} from 'rxjs/operators';
@@ -7,6 +7,8 @@
 import {FormBuilder, FormGroup, Validators} from '@angular/forms';
 import {Confirmation} from '../../../services/confirmation';
 import {Router} from '@angular/router';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatTableDataSource} from '@angular/material/table';
 
 @Component({
   selector: 'app-role-management',
@@ -14,12 +16,13 @@
   templateUrl: './role-management.html',
   styleUrl: './role-management.scss'
 })
-export class RoleManagement implements OnInit {
+export class RoleManagement implements OnInit, AfterViewInit {
   totalRecords: number = 0;
-  roleDataSource: any[] = [];
+  roleDataSource: MatTableDataSource<any> = new MatTableDataSource();
   roleColumns: string[] = ['serial', 'role_name', 'action'];
   dialog = inject(MatDialog);
   dialogConfig = new MatDialogConfig();
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
 
   constructor(
     private systemService: SystemService,
@@ -36,12 +39,17 @@
     })
   }
 
+  ngAfterViewInit() {
+    this.roleDataSource.paginator = this.paginator;
+    this.cdRef.detectChanges();
+  }
+
   getRoles() {
-    this.roleDataSource = [];
+    this.roleDataSource.data = [];
     this.totalRecords = 0;
     this.systemService.getUserRoles().pipe(take(1)).subscribe({
       next: (result: any) => {
-        this.roleDataSource = result;
+        this.roleDataSource.data = result;
         this.totalRecords = result.length;
         this.cdRef.detectChanges();
       },
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/system-backup-restore/system-backup-restore.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/system-backup-restore/system-backup-restore.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/system-backup-restore/system-backup-restore.ts	(working copy)
@@ -1,4 +1,4 @@
-import {ChangeDetectorRef, Component, inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {AfterViewInit, ChangeDetectorRef, Component, inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
 import {SystemService} from '../../../services/system-service';
 import {SharedModule} from '../../../shared/shared-module';
 import {take} from 'rxjs/operators';
@@ -19,7 +19,7 @@
   templateUrl: './system-backup-restore.html',
   styleUrl: './system-backup-restore.scss'
 })
-export class SystemBackupRestore implements OnInit, OnDestroy {
+export class SystemBackupRestore implements OnInit, OnDestroy, AfterViewInit {
 
   scheduledBackupColumns = ['serial', 'location', 'schedule', 'action'];
   scheduledBackupDatasource = new MatTableDataSource();
@@ -34,6 +34,7 @@
     status: '',
     message: '',
   }
+  totalRecords: number = 0;
   private pollingInterval: any;
   private destroy$ = new Subject<void>();
 
@@ -51,18 +52,23 @@
       this.getScheduledBackupFiles();
       this.getSystemRestoreStatus();
     });
+  }
+
+  ngAfterViewInit() {
     this.backupDatasource.paginator = this.paginator;
+    this.cdRef.detectChanges();
   }
 
   getSystemBackupFiles(): void {
-    this.backupDatasource = new MatTableDataSource();
+    this.backupDatasource.data = [];
     this.systemService.getSystemBackupFiles()
       .pipe(take(1))
       .subscribe({
         next: (result: any) => {
           if (result.length > 0) {
-            this.backupDatasource = new MatTableDataSource(result);
+            this.backupDatasource.data = result;
             this.backupDatasource.paginator = this.paginator;
+            this.totalRecords = result.length;
           }
           this.cdRef.detectChanges();
         },
@@ -244,7 +250,8 @@
   }
 
   // ToDo: Implement Restore System Backup functionality.
-  restoreBackup() {}
+  restoreBackup() {
+  }
 
   ngOnDestroy(): void {
     if (this.pollingInterval) {
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/user-management/user-management.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/user-management/user-management.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/user-management/user-management.html	(working copy)
@@ -7,7 +7,7 @@
   <table mat-table [dataSource]="userDataSource" class="mat-elevation-z1">
     <ng-container matColumnDef="serial">
       <th mat-header-cell *matHeaderCellDef>Id.</th>
-      <td mat-cell *matCellDef="let element; let i = index;">{{ i + 1 }}</td>
+      <td mat-cell *matCellDef="let element; let i = index;">{{ getGlobalSerial(i) }}</td>
     </ng-container>
     <ng-container matColumnDef="username">
       <th mat-header-cell *matHeaderCellDef>Username</th>
@@ -50,7 +50,7 @@
     </tr>
   </table>
   <mat-paginator
-    [pageSizeOptions]="[10, 15, 20, 25]"
+    [pageSizeOptions]="[5, 10, 15, 20, 25]"
     [length]="totalRecords"
     showFirstLastButtons
   ></mat-paginator>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/user-management/user-management.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/user-management/user-management.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/user-management/user-management.ts	(working copy)
@@ -1,4 +1,4 @@
-import {ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
+import {AfterViewInit, ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
 import {SharedModule} from '../../../shared/shared-module';
 import {SystemService} from '../../../services/system-service';
 import {NotificationService} from '../../../services/notification';
@@ -8,6 +8,8 @@
 import {take} from 'rxjs/operators';
 import {AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators} from '@angular/forms';
 import {CustomValidators} from '../../../utils/custom-validators';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatTableDataSource} from '@angular/material/table';
 
 @Component({
   selector: 'app-user-management',
@@ -15,15 +17,16 @@
   templateUrl: './user-management.html',
   styleUrl: './user-management.scss'
 })
-export class UserManagement implements OnInit {
+export class UserManagement implements OnInit, AfterViewInit {
 
   totalRecords: number = 0;
 
-  userDataSource: any[] = [];
+  userDataSource: MatTableDataSource<any> = new MatTableDataSource();
   userColumns: string[] = ['serial', 'username', 'type', 'role', 'email', 'action'];
   dialog = inject(MatDialog);
   dialogConfig = new MatDialogConfig();
   roles: any[] = [];
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
 
   constructor(
     private systemService: SystemService,
@@ -39,14 +42,20 @@
       this.getUsers();
       this.getRoles();
     })
+  }
+
+  ngAfterViewInit() {
+    this.userDataSource.paginator = this.paginator;
+    this.cdRef.detectChanges();
   }
 
   getUsers() {
-    this.userDataSource = [];
+    this.userDataSource.data = [];
     this.systemService.getUsers().pipe(take(1)).subscribe({
       next: (result: any) => {
-        this.userDataSource = result;
+        this.userDataSource.data = result;
         this.totalRecords = result.length;
+        this.userDataSource.paginator = this.paginator;
         this.cdRef.detectChanges();
       },
       error: (error: any) => {
@@ -54,6 +63,13 @@
         this.cdRef.detectChanges();
       }
     });
+  }
+
+  getGlobalSerial(index: number): number {
+    if (this.paginator) {
+      return this.paginator.pageIndex * this.paginator.pageSize + index + 1;
+    }
+    return index + 1;
   }
 
   getRoles() {
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/config-discovered-devices.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/config-discovered-devices.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/config-discovered-devices.html	(working copy)
@@ -0,0 +1,65 @@
+<h2 mat-dialog-title>Configure discovered devices {{data?.device?.id}}</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="vlDeviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="licenseName" class="form-label">License *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="licenseName">
+          @for (_license of data?.licenses; track _license) {
+            <mat-option [value]="_license.name">{{ _license.name }}</mat-option>
+          }
+        </mat-select>
+        @if (vlDeviceForm.get('licenseName')?.invalid && vlDeviceForm.get('licenseName')?.touched) {
+          <mat-error>
+            @if (vlDeviceForm.get('licenseName')?.errors?.['required']) {
+              License is required.
+            } @else {
+              Invalid license format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="bandwidth_mode" class="form-label">Bandwidth Mode *</label>
+      <mat-radio-group formControlName="bandwidth_mode">
+        <mat-radio-button value="manual">Manual Allocation</mat-radio-button>
+        <mat-radio-button value="auto">Automatic</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    @if (vlDeviceForm.get('bandwidth_mode')?.value === 'manual') {
+      <div class="form-field-wrapper">
+        <label for="bandwidth_value" class="form-label">Bandwidth (Mbps) *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input id="bandwidth_value" formControlName="bandwidth_value" matInput placeholder="100" type="number"/>
+          @if (vlDeviceForm.get('bandwidth_value')?.invalid && vlDeviceForm.get('bandwidth_value')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('bandwidth_value')?.errors?.['required']) {
+                Bandwidth value is required.
+              } @else if (vlDeviceForm.get('bandwidth_value')?.errors) {
+                Invalid bandwidth value.
+              }
+            </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/vl-discovered-devices/vl-discovered-devices.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.html	(working copy)
@@ -0,0 +1,57 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>Managed Devices</mat-card-title>
+  </mat-card-header>
+  <mat-card-content>
+    <form [formGroup]="devicesForm" class="common-form">
+      <div class="form-field-wrapper">
+        <mat-slide-toggle labelPosition="before" formControlName="auto_mode"
+                          (change)="updateAutoDiscoveredDevice($event)">Auto Enable
+        </mat-slide-toggle>
+      </div>
+    </form>
+  </mat-card-content>
+</mat-card>
+@if (!devicesForm.get('auto_mode')?.value) {
+  <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 + 1 }}</td>
+      </ng-container>
+      <ng-container matColumnDef="ip">
+        <th mat-header-cell *matHeaderCellDef> IP Address</th>
+        <td mat-cell *matCellDef="let element">{{ element.ip }}</td>
+      </ng-container>
+      <ng-container matColumnDef="serial_number">
+        <th mat-header-cell *matHeaderCellDef> Serial Number</th>
+        <td mat-cell *matCellDef="let element"> {{ element.serial_number }}</td>
+      </ng-container>
+      <ng-container matColumnDef="status">
+        <th mat-header-cell *matHeaderCellDef> Status</th>
+        <td mat-cell *matCellDef="let element">{{ element.status | titlecase }}</td>
+      </ng-container>
+      <ng-container matColumnDef="action">
+        <th mat-header-cell *matHeaderCellDef class="action-header w-10"> Action</th>
+        <td mat-cell *matCellDef="let element">
+          <div class="row-action a-link">
+            <fa-icon [icon]="['far', 'play-circle']" size="lg" matTooltip="Enable volume license"
+                     (click)="configureDeviceVolumeLicense(element)"></fa-icon>
+            <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Remove Device"
+                     (click)="removeDevice(element)"></fa-icon>
+          </div>
+        </td>
+      </ng-container>
+      <tr mat-header-row *matHeaderRowDef="deviceColumns"></tr>
+      <tr mat-row *matRowDef="let row; columns: deviceColumns;"></tr>
+      <tr class="mat-row table-no-data" *matNoDataRow>
+        <td class="mat-cell" colspan="11">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/vl-discovered-devices/vl-discovered-devices.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.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/vl-discovered-devices/vl-discovered-devices.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VlDiscoveredDevices } from './vl-discovered-devices';
+
+describe('VlDiscoveredDevices', () => {
+  let component: VlDiscoveredDevices;
+  let fixture: ComponentFixture<VlDiscoveredDevices>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VlDiscoveredDevices]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VlDiscoveredDevices);
+    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/vl-discovered-devices/vl-discovered-devices.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-discovered-devices/vl-discovered-devices.ts	(working copy)
@@ -0,0 +1,239 @@
+import {AfterViewInit, ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {VolumeLicense} from '../../../services/volume-license';
+import {DeviceService} from '../../../services/device-service';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {SharedModule} from '../../../shared/shared-module';
+import {take} from 'rxjs/operators';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatTableDataSource} from '@angular/material/table';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+
+@Component({
+  selector: 'app-vl-discovered-devices',
+  imports: [SharedModule],
+  templateUrl: './vl-discovered-devices.html',
+  styleUrl: './vl-discovered-devices.scss'
+})
+export class VlDiscoveredDevices implements OnInit, AfterViewInit {
+
+  totalRecords: number = 0;
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  deviceColumns: string[] = ['serial', 'ip', 'serial_number', 'status', 'action'];
+
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  volumeLicenses: any = [];
+  devices: any = [];
+  deviceGroups: any = [];
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+
+  devicesForm: FormGroup;
+
+  constructor(
+    private _volume: VolumeLicense,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation,
+    private cdRef: ChangeDetectorRef,
+    private _formBuilder: FormBuilder,
+  ) {
+    this.devicesForm = this._formBuilder.group({
+      auto_mode: [false, [Validators.required]],
+    });
+  }
+
+  ngOnInit() {
+    setTimeout(() => {
+      this.getVLDiscoverDevicesMode();
+      this.getDiscoveredDevices();
+      this.getVolumeLicenses();
+    });
+  }
+
+  ngAfterViewInit() {
+    this.dataSource.paginator = this.paginator;
+    this.cdRef.detectChanges();
+  }
+
+  getVLDiscoverDevicesMode() {
+    this._volume.getVLDiscoverDevicesMode().pipe(take(1)).subscribe({
+      next: (result: any) => {
+        this.devicesForm.patchValue({
+          auto_mode: result.auto_mode,
+        })
+        this.cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this.cdRef.detectChanges();
+      }
+    })
+  }
+
+  getDiscoveredDevices() {
+    this.dataSource.data = [];
+    this.totalRecords = 0;
+    this._volume.getVLDiscoverDevices().pipe(take(1)).subscribe({
+      next: (result: any) => {
+        this.dataSource = result;
+        this.dataSource.paginator = this.paginator;
+        this.totalRecords = result?.length;
+        this.cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this.cdRef.detectChanges();
+      }
+    })
+  }
+
+  getVolumeLicenses() {
+    this.volumeLicenses = [];
+    this._volume.getVolumeLicenses().pipe(take(1)).subscribe({
+      next: (result) => {
+        this.volumeLicenses = result;
+        this.cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this.cdRef.detectChanges();
+      }
+    })
+  }
+
+  updateAutoDiscoveredDevice(event: any) {
+    let rawPayload: any = new FormData();
+    rawPayload.set('auto_mode', this.devicesForm.value.auto_mode);
+    this._volume.updateVLDiscoverDeviceMode(rawPayload).pipe(take(1)).subscribe({
+      next: (result) => {
+        this._notification.showSuccess('The volume license auto enable mode has been updated successfully.');
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    })
+  }
+
+
+  removeDevice(_device: any) {
+    let confirmMsg = `Are you sure you want to delete "${_device?.ip}"?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_device?.ip}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Delete It',
+      cancelButtonText: 'No, Keep It',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe(result => {
+      if (result) {
+        let rawPayload: any = new FormData();
+        rawPayload.set('pk', JSON.stringify({
+          ip: _device.ip,
+        }));
+        this._volume.deleteVLDiscoverDevice(rawPayload).pipe(take(1)).subscribe({
+          next: (result) => {
+            // Fix it in backend
+          },
+          error: (error: any) => {
+            if (error.status === 200) {
+              this._notification.showSuccess('The device has been removed successfully from the discovered devices.');
+              this.getDiscoveredDevices();
+            } else {
+              this._notification.showError(`Error: ${error?.message}`);
+            }
+          }
+        })
+      }
+    })
+  }
+
+  configureDeviceVolumeLicense(_device: any) {
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = {
+      licenses: this.volumeLicenses,
+      device: _device,
+    }
+    const dialogRef = this.dialog.open(ConfiguredDiscoveredDeviceDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getDiscoveredDevices();
+      }
+    })
+  }
+}
+
+@Component({
+  selector: 'config-discovered-devices',
+  templateUrl: './config-discovered-devices.html',
+  imports: [SharedModule]
+})
+export class ConfiguredDiscoveredDeviceDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<ConfiguredDiscoveredDeviceDialog>);
+
+  vlDeviceForm!: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    console.log(this.data)
+    this.vlDeviceForm = this._formBuilder.group({
+      licenseName: ['', [Validators.required]],
+      bandwidth_mode: ['manual', [Validators.required]],
+      bandwidth_value: [0, [Validators.required]],
+    })
+  }
+
+  onSubmit(): void {
+    if (this.vlDeviceForm.invalid) {
+      console.log(this.vlDeviceForm.value);
+      this.vlDeviceForm.markAllAsTouched();
+      return;
+    }
+    console.log(this.vlDeviceForm.value);
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'Activate');
+    rawPayload.set('post_data', JSON.stringify({
+      "__pk_list": [
+        JSON.stringify({
+          ip: this.data?.device?.ip,
+        })
+      ],
+      license_name: this.vlDeviceForm.value.licenseName,
+      restapi_protocol: this.data?.device?.restapi_config?.restapi_protocol,
+      restapi_port: this.data?.device?.restapi_config?.restapi_port,
+      restapi_username: this.data?.device?.restapi_config?.restapi_username,
+      restapi_password: this.data?.device?.restapi_config?.restapi_password,
+      bandwidth: this.vlDeviceForm.value.bandwidth_value,
+    }));
+    this._volume.activateVLDiscoverDevice(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result[0]) {
+            this._notification.showSuccess('The device license has been activated.');
+            this.dialogRef.close(true);
+          } else {
+            this._notification.showError(result[1]);
+          }
+        },
+        error: (err) => {
+          console.error(err);
+          this._notification.showError('Failed to activate device license.');
+        }
+      })
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/add-vl-device.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/add-vl-device.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/add-vl-device.html	(working copy)
@@ -0,0 +1,179 @@
+<h2 mat-dialog-title>Add Device</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="vlDeviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="source" class="form-label">Source *</label>
+      <mat-radio-group formControlName="source">
+        <mat-radio-button value="existing">Select from AMP Devices List</mat-radio-button>
+        <mat-radio-button value="new">Add using Device REST API</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    @if (vlDeviceForm.get('source')?.value === 'existing') {
+      <div class="form-field-wrapper">
+        <label for="deviceName" class="form-label">Device *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <mat-select formControlName="deviceName">
+            @for (_device of data?.devices; track _device) {
+              <mat-option [value]="_device.name">{{ _device.name + ' (' + _device.ip + ')' }}</mat-option>
+            }
+          </mat-select>
+          @if (vlDeviceForm.get('deviceName')?.invalid && vlDeviceForm.get('deviceName')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('deviceName')?.errors?.['required']) {
+                Device is required.
+              } @else {
+                Invalid device format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+    } @else {
+      <div class="form-field-wrapper">
+        <label for="ipAddress" class="form-label">IP Address *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="ipAddress"
+            formControlName="ipAddress"
+            matInput
+            placeholder="IP Address"
+            type="text"
+          />
+          @if (vlDeviceForm.get('ipAddress')?.invalid && vlDeviceForm.get('ipAddress')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('ipAddress')?.errors?.['required']) {
+                IP address is required.
+              } @else if (vlDeviceForm.get('ipAddress')?.errors) {
+                Invalid IP address format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="protocol" class="form-label">Protocol *</label>
+        <mat-radio-group formControlName="protocol">
+          <mat-radio-button value="https">HTTPS</mat-radio-button>
+          <mat-radio-button value="http">HTTP</mat-radio-button>
+        </mat-radio-group>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="restapi_port" class="form-label">API Port *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input id="restapi_port" formControlName="restapi_port" matInput placeholder="9997" type="number"/>
+          @if (vlDeviceForm.get('restapi_port')?.invalid && vlDeviceForm.get('restapi_port')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('restapi_port')?.errors?.['required']) {
+                Port number is required.
+              } @else if (vlDeviceForm.get('restapi_port')?.errors) {
+                Invalid port number, should range from 0-65535.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="restapi_username" class="form-label">API Username *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="restapi_username"
+            formControlName="restapi_username"
+            matInput
+            placeholder="API Username"
+            type="text"
+          />
+          @if (vlDeviceForm.get('restapi_username')?.invalid && vlDeviceForm.get('restapi_username')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('restapi_username')?.errors?.['required']) {
+                Username is required.
+              } @else {
+                Invalid username format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="restapi_password" class="form-label">API Password *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="restapi_password"
+            formControlName="restapi_password"
+            matInput
+            placeholder="API Password"
+            type="password"
+          />
+          @if (vlDeviceForm.get('restapi_password')?.invalid && vlDeviceForm.get('restapi_password')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('restapi_password')?.errors?.['required']) {
+                Password is required.
+              } @else {
+                Invalid password format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="licenseName" class="form-label">License *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <mat-select formControlName="licenseName">
+            @for (_license of data?.licenses; track _license) {
+              <mat-option [value]="_license.name">{{ _license.name }}</mat-option>
+            }
+          </mat-select>
+          @if (vlDeviceForm.get('licenseName')?.invalid && vlDeviceForm.get('licenseName')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('licenseName')?.errors?.['required']) {
+                License is required.
+              } @else {
+                Invalid license format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="bandwidth_mode" class="form-label">Bandwidth Mode *</label>
+        <mat-radio-group formControlName="bandwidth_mode">
+          <mat-radio-button value="manual">Manual Allocation</mat-radio-button>
+          <mat-radio-button value="auto">Automatic</mat-radio-button>
+        </mat-radio-group>
+      </div>
+      @if (vlDeviceForm.get('bandwidth_mode')?.value === 'manual') {
+        <div class="form-field-wrapper">
+          <label for="bandwidth_value" class="form-label">Bandwidth (Mbps) *</label>
+          <mat-form-field appearance="outline" subscriptSizing="dynamic">
+            <input id="bandwidth_value" formControlName="bandwidth_value" matInput placeholder="100" type="number"/>
+            @if (vlDeviceForm.get('bandwidth_value')?.invalid && vlDeviceForm.get('bandwidth_value')?.touched) {
+              <mat-error>
+                @if (vlDeviceForm.get('bandwidth_value')?.errors?.['required']) {
+                  Bandwidth value is required.
+                } @else if (vlDeviceForm.get('bandwidth_value')?.errors) {
+                  Invalid bandwidth value.
+                }
+              </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/vl-managed-devices/edit-vl-device-bandwidth.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/edit-vl-device-bandwidth.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/edit-vl-device-bandwidth.html	(working copy)
@@ -0,0 +1,46 @@
+<h2 mat-dialog-title>Edit device Bandwidth - {{ data?.device?.id }}</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="vlDeviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="bandwidth_mode" class="form-label">Bandwidth Mode *</label>
+      <mat-radio-group formControlName="bandwidth_mode">
+        <mat-radio-button value="manual">Manual Allocation</mat-radio-button>
+        <mat-radio-button value="auto">Automatic</mat-radio-button>
+      </mat-radio-group>
+    </div>
+    @if (vlDeviceForm.get('bandwidth_mode')?.value === 'manual') {
+      <div class="form-field-wrapper">
+        <label for="bandwidth_value" class="form-label">Bandwidth (Mbps) *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input id="bandwidth_value" formControlName="bandwidth_value" matInput placeholder="100" type="number"/>
+          @if (vlDeviceForm.get('bandwidth_value')?.invalid && vlDeviceForm.get('bandwidth_value')?.touched) {
+            <mat-error>
+              @if (vlDeviceForm.get('bandwidth_value')?.errors?.['required']) {
+                Bandwidth value is required.
+              } @else if (vlDeviceForm.get('bandwidth_value')?.errors) {
+                Invalid bandwidth value.
+              }
+            </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/vl-managed-devices/enable-device-vl.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/enable-device-vl.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/enable-device-vl.html	(working copy)
@@ -0,0 +1,41 @@
+<h2 mat-dialog-title>Enable license for Device - {{ data?.device?.id }}</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="vlDeviceForm"
+  >
+    <div class="form-field-wrapper">
+      <label for="licenseName" class="form-label">License *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <mat-select formControlName="licenseName">
+          @for (_license of data?.licenses; track _license) {
+            <mat-option [value]="_license.name">{{ _license.name }}</mat-option>
+          }
+        </mat-select>
+        @if (vlDeviceForm.get('licenseName')?.invalid && vlDeviceForm.get('licenseName')?.touched) {
+          <mat-error>
+            @if (vlDeviceForm.get('licenseName')?.errors?.['required']) {
+              License is required.
+            } @else {
+              Invalid license 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/vl-managed-devices/show-vl-device.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/show-vl-device.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/show-vl-device.html	(working copy)
@@ -0,0 +1,33 @@
+<h2 mat-dialog-title>Device details - {{ this.data?.device?.id }}</h2>
+<mat-dialog-content>
+  <table mat-table [dataSource]="deviceDataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="key">
+      <td mat-cell *matCellDef="let element"> {{ element.key }}</td>
+    </ng-container>
+    <ng-container matColumnDef="value">
+      <td mat-cell *matCellDef="let element">
+        <span [innerHTML]="element.value | newlineToBr"></span>
+      </td>
+    </ng-container>
+    <tr mat-row *matRowDef="let row; columns: deviceColumns;"></tr>
+  </table>
+  <br>
+  <p><b>Detailed device information:</b></p>
+  <div [innerHTML]="deviceMoreDetails | newlineToBr" class="wrapped-text"></div>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+</mat-dialog-actions>
+
+<style>
+  .wrapped-text {
+    word-wrap: break-word;
+    overflow-wrap: break-word;
+  }
+
+</style>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.html	(working copy)
@@ -0,0 +1,102 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>Managed Devices</mat-card-title>
+    <div>
+      <button mat-raised-button (click)="addDevice()">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 + 1 }}</td>
+    </ng-container>
+    <ng-container matColumnDef="ip">
+      <th mat-header-cell *matHeaderCellDef> IP Address</th>
+      <td mat-cell *matCellDef="let element">
+        <a class="details-page-link" (click)="goToDetails(element)">{{ element.ip }}</a>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="product">
+      <th mat-header-cell *matHeaderCellDef> Product Name</th>
+      <td mat-cell *matCellDef="let element"> {{ element.product_name }}</td>
+    </ng-container>
+    <ng-container matColumnDef="connection">
+      <th mat-header-cell *matHeaderCellDef> Connection</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          @if (element.connection === 'connected') {
+            <fa-icon [icon]="['far', 'check-circle']" class="success-icon"></fa-icon>
+          } @else {
+            <fa-icon [icon]="['far', 'xmark-circle']" class="delete-icon"></fa-icon>
+          }
+        </div>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="license">
+      <th mat-header-cell *matHeaderCellDef> License</th>
+      <td mat-cell *matCellDef="let element">{{ element.license_name?.length > 0 ? element.license_name : 'N/A' }}</td>
+    </ng-container>
+    <ng-container matColumnDef="status">
+      <th mat-header-cell *matHeaderCellDef> Status</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          @if (element?.status === 'enabled') {
+            <fa-icon [icon]="['far', 'check-circle']" class="success-icon"></fa-icon>
+          } @else {
+            <fa-icon [icon]="['far', 'xmark-circle']" class="delete-icon"></fa-icon>
+          }
+        </div>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="mode">
+      <th mat-header-cell *matHeaderCellDef> Bandwidth Mode</th>
+      <td mat-cell *matCellDef="let element"> {{ element.auto_bw ? 'Auto' : 'Manual' }}</td>
+    </ng-container>
+    <ng-container matColumnDef="assigned">
+      <th mat-header-cell *matHeaderCellDef> Assigned Bandwidth</th>
+      <td mat-cell *matCellDef="let element"> {{ element.bw_limit ? element.bw_limit + ' Mbps' : 'N/A' }}</td>
+    </ng-container>
+    <ng-container matColumnDef="applied">
+      <th mat-header-cell *matHeaderCellDef> Applied Bandwidth</th>
+      <td mat-cell *matCellDef="let element"> {{ element.perbw }}</td>
+    </ng-container>
+    <ng-container matColumnDef="real">
+      <th mat-header-cell *matHeaderCellDef> Real-time Bandwidth</th>
+      <td mat-cell *matCellDef="let element">
+        Current: {{ element.rel_bandwith?.current }} <br>
+        Dropped: {{ element.rel_bandwith?.dropped }} <br>
+        Limitation: {{ element.rel_bandwith?.limitation }} <br>
+      </td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef class="action-header w-10"> Action</th>
+      <td mat-cell *matCellDef="let element">
+        <div class="row-action a-link">
+          @if (element?.status === 'enabled') {
+            <fa-icon [icon]="['far', 'stop-circle']" size="lg" matTooltip="Disable volume license"
+                     (click)="disableDeviceVolumeLicense(element)"></fa-icon>
+          } @else {
+            <fa-icon [icon]="['far', 'play-circle']" size="lg" matTooltip="Enable volume license"
+                     (click)="enableDeviceVolumeLicense(element)"></fa-icon>
+          }
+          <fa-icon [icon]="['far', 'edit']" size="lg" matTooltip="Update Device Bandwidth"
+                   (click)="editDeviceBandwidth(element)"></fa-icon>
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Remove Device"
+                   (click)="removeDevice(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="deviceColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: deviceColumns;"></tr>
+    <tr class="mat-row table-no-data" *matNoDataRow>
+      <td class="mat-cell" colspan="11">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/vl-managed-devices/vl-managed-devices.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.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/vl-managed-devices/vl-managed-devices.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VlManagedDevices } from './vl-managed-devices';
+
+describe('VlManagedDevices', () => {
+  let component: VlManagedDevices;
+  let fixture: ComponentFixture<VlManagedDevices>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VlManagedDevices]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VlManagedDevices);
+    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/vl-managed-devices/vl-managed-devices.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/vl-managed-devices/vl-managed-devices.ts	(working copy)
@@ -0,0 +1,614 @@
+import {AfterViewInit, ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../../shared/shared-module';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {VolumeLicense} from '../../../services/volume-license';
+import {NotificationService} from '../../../services/notification';
+import {Confirmation} from '../../../services/confirmation';
+import {take} from 'rxjs/operators';
+import {DeviceService} from '../../../services/device-service';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {CustomValidators} from '../../../utils/custom-validators';
+import {MatTableDataSource} from '@angular/material/table';
+import {NewlineToBrPipe} from '../../../pipes/newline-to-br-pipe';
+import {MatPaginator} from '@angular/material/paginator';
+
+@Component({
+  selector: 'app-vl-managed-devices',
+  imports: [
+    SharedModule
+  ],
+  templateUrl: './vl-managed-devices.html',
+  styleUrl: './vl-managed-devices.scss'
+})
+export class VlManagedDevices implements OnInit, AfterViewInit {
+
+  totalRecords: number = 0;
+  dataSource: MatTableDataSource<any> = new MatTableDataSource();
+  deviceColumns: string[] = ['serial', 'ip', 'product', 'connection', 'license', 'status', 'mode', 'assigned', 'applied', 'real', 'action'];
+
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  volumeLicenses: any = [];
+  devices: any = [];
+  deviceGroups: any = [];
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+
+  constructor(
+    private _volume: VolumeLicense,
+    private _device: DeviceService,
+    private _notification: NotificationService,
+    private _confirmation: Confirmation,
+    private cdRef: ChangeDetectorRef
+  ) {
+  }
+
+  ngOnInit() {
+    setTimeout(() => {
+      this.getManagedDevices();
+      this.getVolumeLicenses();
+      this.getDeviceGroups();
+    });
+  }
+
+  ngAfterViewInit() {
+    this.dataSource.paginator = this.paginator;
+    this.cdRef.detectChanges();
+  }
+
+  getManagedDevices() {
+    this.dataSource.data = [];
+    this.totalRecords = 0;
+    this._volume.getManagedDevices().pipe(take(1)).subscribe({
+      next: (result: any) => {
+        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();
+      }
+    })
+  }
+
+  getVolumeLicenses() {
+    this.volumeLicenses = [];
+    this._volume.getVolumeLicenses().pipe(take(1)).subscribe({
+      next: (result) => {
+        this.volumeLicenses = result;
+        this.cdRef.detectChanges();
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+        this.cdRef.detectChanges();
+      }
+    })
+  }
+
+  getDeviceGroups(): void {
+    this.devices = [];
+    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) => {
+                this.deviceGroups.push(group?.group_name);
+                group?.device_list.forEach((device: any) => {
+                  this.devices.push(device);
+                })
+              })
+            }
+          }
+        }, error: (error: { message: string; }) => {
+          this._notification.showError(error.message);
+        }
+      })
+  }
+
+  addDevice() {
+    this.dialogConfig.position = {
+      bottom: '0px', right: '0px',
+    }
+    this.dialogConfig.width = '50%';
+    this.dialogConfig.height = '80%';
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = {
+      devices: this.devices,
+      licenses: this.volumeLicenses,
+    }
+    const dialogRef = this.dialog.open(AddVLManagedDeviceDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getManagedDevices();
+      }
+    })
+  }
+
+  goToDetails(_device: any) {
+    this.dialogConfig.position = {
+      bottom: '0px', right: '0px',
+    }
+    this.dialogConfig.width = '50%';
+    this.dialogConfig.height = '80%';
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = {
+      device: _device,
+    }
+    const dialogRef = this.dialog.open(ShowVLManagedDeviceDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe(() => {
+    });
+  }
+
+  editDeviceBandwidth(_device: any) {
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = {
+      device: _device,
+    }
+    const dialogRef = this.dialog.open(EditVLManagedDeviceBandwidthDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getManagedDevices();
+      }
+    })
+  }
+
+  enableDeviceVolumeLicense(_device: any) {
+    this.dialogConfig.disableClose = true;
+    this.dialogConfig.data = {
+      licenses: this.volumeLicenses,
+      device: _device,
+    }
+    const dialogRef = this.dialog.open(EnableVLManagedDeviceDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getManagedDevices();
+      }
+    })
+  }
+
+  disableDeviceVolumeLicense(_device: any) {
+    let confirmMsg = `Are you sure you want to disable the volume license "${_device?.id}"?`
+    this._confirmation.openConfirmDialog({
+      title: `Disable volume license for - ${_device?.id}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Disable It',
+      cancelButtonText: 'No, Keep It',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe((result: boolean) => {
+      if (result) {
+        let rawPayload = new FormData();
+        rawPayload.set('action', 'Deactivate');
+        rawPayload.set('options', JSON.stringify({
+          "__pk_list": [JSON.stringify({id: _device?.id})],
+        }));
+        this._volume.deactivateDeviceVolumeLicense(rawPayload)
+          .pipe(take(1))
+          .subscribe({
+            next: (result: any) => {
+              if (result[0]) {
+                this._notification.showSuccess(`The device license has been disabled successfully!`);
+                this.getManagedDevices();
+              } else {
+                this._notification.showError('Deletion Failed.');
+              }
+            },
+            error: (err) => {
+              this._notification.showError('Deletion Failed.');
+            }
+          })
+      } else {
+        this._notification.showSuccess('Deletion cancelled.');
+      }
+    });
+  }
+
+  removeDevice(_device: any) {
+    let confirmMsg = `Are you sure you want to permanently delete "${_device?.id}"?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete ${_device?.id}?`,
+      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({
+          "id": _device?.id,
+        }));
+        this._volume.removeVLManagedDevice(rawPayload)
+          .pipe(take(1))
+          .subscribe({
+            next: (result: any) => {
+            },
+            error: (err) => {
+              if (err.status === 200) {
+                // ToDo: Backend fix required.
+                this._notification.showSuccess(`${_device?.id} deleted successfully!`);
+                this.getManagedDevices();
+              } else {
+                this._notification.showError('Deletion Failed.');
+              }
+            }
+          })
+      } else {
+        this._notification.showSuccess('Deletion cancelled.');
+      }
+    });
+  }
+}
+
+@Component({
+  selector: 'add-vl-device',
+  templateUrl: './add-vl-device.html',
+  imports: [SharedModule]
+})
+export class AddVLManagedDeviceDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<AddVLManagedDeviceDialog>);
+
+  vlDeviceForm!: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit(): void {
+    this.vlDeviceForm = this._formBuilder.group({
+      source: ['existing', Validators.required],
+      deviceName: ['', Validators.required],
+      licenseName: [''],
+      protocol: ['https'],
+      restapi_port: [9997, [Validators.min(0), Validators.max(65535)]],
+      ipAddress: ['', [CustomValidators.ipv4()]],
+      restapi_username: [''],
+      restapi_password: [''],
+      bandwidth_mode: ['manual'],
+      bandwidth_value: [0],
+    });
+
+    // Subscribe to changes in the 'source' control
+    this.vlDeviceForm.get('source')!.valueChanges.subscribe(sourceValue => {
+      this.updateValidators(sourceValue);
+    });
+
+    // Call it once initially to set the correct validators based on the default 'existing' value
+    this.updateValidators(this.vlDeviceForm.get('source')!.value);
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  onSubmit() {
+    if (this.vlDeviceForm.invalid) {
+      console.log(this.vlDeviceForm.value);
+      this.vlDeviceForm.markAllAsTouched();
+      return;
+    }
+    if (this.vlDeviceForm.value.source == 'existing') {
+      this.importVLManagedDevice();
+    } else {
+      this.addVLManagedDevice();
+    }
+  }
+
+  addVLManagedDevice(): void {
+    let rawPayload = new FormData();
+    rawPayload.set('post_data', JSON.stringify({
+      id: this.vlDeviceForm.value.ipAddress,
+      ip: this.vlDeviceForm.value.ipAddress,
+      restapi_config: {
+        restapi_username: this.vlDeviceForm.value.restapi_username,
+        restapi_password: this.vlDeviceForm.value.restapi_password,
+        restapi_protocol: this.vlDeviceForm.value.protocol,
+        restapi_port: this.vlDeviceForm.value.restapi_port,
+      },
+      license_name: [
+        {name: this.vlDeviceForm.value.licenseName}
+      ],
+      bw_limit: this.vlDeviceForm.value.bandwidth_value
+    }));
+    this._volume.addVLManagedDevice(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          this._notification.showSuccess('The device has been imported successfully.');
+          this.dialogRef.close(true);
+        },
+        error: (err) => {
+          console.error(err);
+          this._notification.showError('Failed to import the device from the AMP devices list.');
+        }
+      })
+  }
+
+  importVLManagedDevice() {
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'action');
+    rawPayload.set('options', JSON.stringify({device_list: [this.vlDeviceForm.value.deviceName]}));
+    this._volume.importVLManagedDevice(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          this._notification.showSuccess('The device has been imported successfully.');
+          this.dialogRef.close(true);
+        },
+        error: (err) => {
+          console.error(err);
+          this._notification.showError('Failed to import the device from the AMP devices list.');
+        }
+      })
+  }
+
+  private updateValidators(sourceValue: string): void {
+    const deviceNameControl = this.vlDeviceForm.get('deviceName')!;
+    const licenseNameControl = this.vlDeviceForm.get('licenseName')!;
+    const protocolControl = this.vlDeviceForm.get('protocol')!;
+    const restapiPortControl = this.vlDeviceForm.get('restapi_port')!;
+    const ipAddressControl = this.vlDeviceForm.get('ipAddress')!;
+    const restapiUsernameControl = this.vlDeviceForm.get('restapi_username')!;
+    const restapiPasswordControl = this.vlDeviceForm.get('restapi_password')!;
+    const bandwidthModeControl = this.vlDeviceForm.get('bandwidth_mode')!;
+    const bandwidthValueControl = this.vlDeviceForm.get('bandwidth_value')!;
+
+    // Clear all 'required' validators first for controls that will be conditionally required
+    licenseNameControl.clearValidators();
+    protocolControl.clearValidators();
+    restapiPortControl.clearValidators();
+    ipAddressControl.clearValidators();
+    restapiUsernameControl.clearValidators();
+    restapiPasswordControl.clearValidators();
+    bandwidthModeControl.clearValidators();
+    bandwidthValueControl.clearValidators();
+
+    if (sourceValue === 'existing') {
+      // For 'existing' source: deviceName is required
+      deviceNameControl.setValidators(Validators.required);
+
+      // Other fields should not be required
+      licenseNameControl.setValidators(null);
+      protocolControl.setValidators(null);
+      restapiPortControl.setValidators([Validators.min(0), Validators.max(65535)]); // Keep other validators
+      ipAddressControl.setValidators([CustomValidators.ipv4()]); // Keep other validators
+      restapiUsernameControl.setValidators(null);
+      restapiPasswordControl.setValidators(null);
+      bandwidthModeControl.setValidators(null);
+      bandwidthValueControl.setValidators(null);
+
+    } else if (sourceValue === 'new') {
+      // For 'new' source: deviceName is NOT required
+      deviceNameControl.setValidators(null);
+
+      // Other fields ARE required
+      licenseNameControl.setValidators(Validators.required);
+      protocolControl.setValidators(Validators.required);
+      restapiPortControl.setValidators([Validators.required, Validators.min(0), Validators.max(65535)]);
+      ipAddressControl.setValidators([Validators.required, CustomValidators.ipv4()]);
+      restapiUsernameControl.setValidators(Validators.required);
+      restapiPasswordControl.setValidators(Validators.required);
+      bandwidthModeControl.setValidators(Validators.required);
+      bandwidthValueControl.setValidators(Validators.required);
+    }
+
+    // Update validity for all affected controls to re-evaluate their validation status
+    deviceNameControl.updateValueAndValidity();
+    licenseNameControl.updateValueAndValidity();
+    protocolControl.updateValueAndValidity();
+    restapiPortControl.updateValueAndValidity();
+    ipAddressControl.updateValueAndValidity();
+    restapiUsernameControl.updateValueAndValidity();
+    restapiPasswordControl.updateValueAndValidity();
+    bandwidthModeControl.updateValueAndValidity();
+    bandwidthValueControl.updateValueAndValidity();
+  }
+}
+
+@Component({
+  selector: 'enable-device-vl',
+  templateUrl: './enable-device-vl.html',
+  imports: [SharedModule]
+})
+export class EnableVLManagedDeviceDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<EnableVLManagedDeviceDialog>);
+
+  vlDeviceForm!: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.vlDeviceForm = this._formBuilder.group({
+      licenseName: ['', [Validators.required]],
+    })
+  }
+
+  onSubmit(): void {
+    if (this.vlDeviceForm.invalid) {
+      console.log(this.vlDeviceForm.value);
+      this.vlDeviceForm.markAllAsTouched();
+      return;
+    }
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'Activate');
+    rawPayload.set('options', JSON.stringify({
+      "__pk_list": [JSON.stringify({
+        id: this.data.device?.id,
+      })],
+      license_name: this.vlDeviceForm.value.licenseName,
+    }));
+    this._volume.activateDeviceVolumeLicense(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result[0]) {
+            this._notification.showSuccess('The device license has been activated.');
+            this.dialogRef.close(true);
+          } else {
+            this._notification.showError(result[1]);
+          }
+        },
+        error: (err) => {
+          console.error(err);
+          this._notification.showError('Failed to activate device license.');
+        }
+      })
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
+
+@Component({
+  selector: 'edit-vl-device-bandwidth',
+  templateUrl: './edit-vl-device-bandwidth.html',
+  imports: [SharedModule]
+})
+export class EditVLManagedDeviceBandwidthDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<EditVLManagedDeviceBandwidthDialog>);
+
+  vlDeviceForm!: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+  ) {
+  }
+
+  ngOnInit() {
+    this.vlDeviceForm = this._formBuilder.group({
+      bandwidth_mode: ['manual', [Validators.required]],
+      bandwidth_value: [0, [Validators.required]],
+    });
+    this.vlDeviceForm.patchValue({
+      bandwidth_mode: this.data.device.auto_bw ? 'auto' : 'manual',
+      bandwidth_value: this.data.device.bw_limit,
+    })
+  }
+
+  onSubmit(): void {
+    if (this.vlDeviceForm.invalid) {
+      console.log(this.vlDeviceForm.value);
+      this.vlDeviceForm.markAllAsTouched();
+      return;
+    }
+    let rawPayload = new FormData();
+    rawPayload.set('post_data', JSON.stringify({
+      "bw_limit": this.vlDeviceForm.value.bandwidth_value,
+    }));
+    this._volume.updateVLManagedDeviceBandwidth(this.data?.device?.id, rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result[0]) {
+            this._notification.showSuccess('The device bandwidth has been updated successfully.');
+            this.dialogRef.close(true);
+          } else {
+            this._notification.showError(result[1]);
+          }
+        },
+        error: (err) => {
+          console.error(err);
+          this._notification.showError('Failed to update the device bandwidth.');
+        }
+      })
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
+
+@Component({
+  selector: 'show-vl-device',
+  templateUrl: './show-vl-device.html',
+  imports: [SharedModule, NewlineToBrPipe]
+})
+export class ShowVLManagedDeviceDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<ShowVLManagedDeviceDialog>);
+
+  deviceDataSource: any = new MatTableDataSource();
+  deviceColumns: Array<string> = ['key', 'value'];
+
+  deviceDetails: any = [];
+  deviceMoreDetails: any = null;
+
+  constructor(
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+    private cdRef: ChangeDetectorRef,
+  ) {
+  }
+
+  ngOnInit() {
+    setTimeout(() => {
+      this.getVLManagedDeviceVersion();
+    })
+    this.deviceDetails = [
+      {key: 'Device IP', value: this.data?.device?.ip},
+      {key: 'Product', value: this.data?.device?.product_name},
+      {key: 'Model', value: this.data?.device?.model},
+      {key: 'Serial Number', value: this.data?.device?.serial_number},
+      {key: 'Interface', value: this.data?.device?.network_interface},
+      {key: 'Build', value: this.data?.device?.build_version},
+      {key: 'Connection status', value: this.data?.device?.connection == 'connected' ? 'Connected' : this.data?.device?.connection},
+      {key: 'Volume license status', value: this.data?.device?.status == 'enabled' ? 'Enabled' : this.data?.device?.status},
+      {key: 'Boot time', value: this.data?.device?.system_boot_time},
+      {key: 'Up time', value: this.data?.device?.system_up_time},
+      {key: 'Bandwidth limit', value: this.data?.device?.bw_limit === 0 ? 'Automatic' : this.data?.device?.bw_limit + ' Mbps' },
+    ];
+    this.deviceDataSource = new MatTableDataSource(this.deviceDetails);
+  }
+
+  getVLManagedDeviceVersion() {
+    this.deviceMoreDetails = null;
+    let rawPayload = new FormData();
+    rawPayload.set('device_id', this.data.device.id);
+    this._volume.getVLManagedDeviceVersion(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          this.deviceMoreDetails = result?.version;
+          this.cdRef.detectChanges();
+        },
+        error: (err) => {
+          console.error(err);
+          this._notification.showError('Failed to fetch the device version.');
+          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/volume-license-devices-overview/volume-license-devices-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.html	(nonexistent)
@@ -1 +0,0 @@
-<p>volume-license-devices-overview works!</p>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.html	(working copy)
@@ -0,0 +1,18 @@
+<div class="tab-container">
+  <mat-tab-group animationDuration="0ms" [selectedIndex]="selectedTabIndex" (selectedTabChange)="onTabChange($event)">
+    <mat-tab label="Managed Devices">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vl-managed-devices/>
+        </div>
+      </ng-template>
+    </mat-tab>
+    <mat-tab label="Discovered Devices">
+      <ng-template matTabContent>
+        <div class="tab-content">
+          <app-vl-discovered-devices/>
+        </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/volume-license-devices-overview/volume-license-devices-overview.scss	(deleted)
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.scss	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.scss	(working copy)
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.spec.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.spec.ts	(nonexistent)
@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { VolumeLicenseDevicesOverview } from './volume-license-devices-overview';
-
-describe('VolumeLicenseDevicesOverview', () => {
-  let component: VolumeLicenseDevicesOverview;
-  let fixture: ComponentFixture<VolumeLicenseDevicesOverview>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [VolumeLicenseDevicesOverview]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(VolumeLicenseDevicesOverview);
-    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/volume-license-devices-overview/volume-license-devices-overview.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VolumeLicenseDevicesOverview } from './volume-license-devices-overview';
+
+describe('VolumeLicenseDevicesOverview', () => {
+  let component: VolumeLicenseDevicesOverview;
+  let fixture: ComponentFixture<VolumeLicenseDevicesOverview>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VolumeLicenseDevicesOverview]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VolumeLicenseDevicesOverview);
+    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/volume-license-devices-overview/volume-license-devices-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.ts	(nonexistent)
@@ -1,11 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'app-volume-license-devices-overview',
-  imports: [],
-  templateUrl: './volume-license-devices-overview.html',
-  styleUrl: './volume-license-devices-overview.scss'
-})
-export class VolumeLicenseDevicesOverview {
-
-}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-devices-overview/volume-license-devices-overview.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.ts	(working copy)
@@ -0,0 +1,56 @@
+import {Component, OnInit} from '@angular/core';
+import {SharedModule} from '../../shared/shared-module';
+import {VlDiscoveredDevices} from '../sub-components/vl-discovered-devices/vl-discovered-devices';
+import {VlManagedDevices} from '../sub-components/vl-managed-devices/vl-managed-devices';
+import {ActivatedRoute, Router} from '@angular/router';
+import {MatTabChangeEvent} from '@angular/material/tabs';
+
+@Component({
+  selector: 'app-volume-license-devices-overview',
+  imports: [SharedModule, VlDiscoveredDevices, VlManagedDevices],
+  templateUrl: './volume-license-devices-overview.html',
+  styleUrl: './volume-license-devices-overview.scss'
+})
+export class VolumeLicenseDevicesOverview implements OnInit {
+
+  selectedTabIndex: number = 0;
+  private tabNames: string[] = [
+    'Managed Devices',
+    'Discovered Devices',
+  ];
+
+  constructor(private route: ActivatedRoute, private router: Router) {
+  }
+
+  ngOnInit(): void {
+    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);
+  }
+
+  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/volume-license-overview/volume-license-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.html	(nonexistent)
@@ -1 +0,0 @@
-<p>volume-license-overview works!</p>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-overview.html	(working copy)
@@ -0,0 +1,69 @@
+<mat-card class="page-card-1" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>Volume Licenses</mat-card-title>
+    <div>
+      <button mat-raised-button (click)="addVolumeLicense()">Add</button>
+    </div>
+  </mat-card-header>
+</mat-card>
+<div class="table-container">
+  <table mat-table [dataSource]="vlDataSource" 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 + 1 }}</td>
+    </ng-container>
+    <ng-container matColumnDef="name">
+      <th mat-header-cell *matHeaderCellDef> 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="gDate">
+      <th mat-header-cell *matHeaderCellDef> Generation Date</th>
+      <td mat-cell *matCellDef="let element"> {{ element.generation_date | yyyymmdd | date:'MMMM d, y' }}</td>
+    </ng-container>
+    <ng-container matColumnDef="eDate">
+      <th mat-header-cell *matHeaderCellDef> Expiration Date</th>
+      <td mat-cell *matCellDef="let element"> {{ element.expiration_date | yyyymmdd | date:'MMMM d, y' }}</td>
+    </ng-container>
+    <ng-container matColumnDef="bwLimit">
+      <th mat-header-cell *matHeaderCellDef> Bandwidth Limitation</th>
+      <td mat-cell *matCellDef="let element"> {{ element.bandwidth }}</td>
+    </ng-container>
+    <ng-container matColumnDef="availableBandwidth">
+      <th mat-header-cell *matHeaderCellDef> Available bandwidth</th>
+      <td mat-cell *matCellDef="let element"> {{ element.available_bw }} Mbps</td>
+    </ng-container>
+    <ng-container matColumnDef="usedBandwidth">
+      <th mat-header-cell *matHeaderCellDef> Allocated bandwidth</th>
+      <td mat-cell *matCellDef="let element"> {{ element.used_bw }} Mbps</td>
+    </ng-container>
+    <ng-container matColumnDef="capacity">
+      <th mat-header-cell *matHeaderCellDef> vAPV Capacity</th>
+      <td mat-cell *matCellDef="let element"> {{ element.capacity }}</td>
+    </ng-container>
+    <ng-container matColumnDef="memoryControl">
+      <th mat-header-cell *matHeaderCellDef> Memory Control</th>
+      <td mat-cell *matCellDef="let element"> {{ element.memory_control }}</td>
+    </ng-container>
+    <ng-container matColumnDef="action">
+      <th mat-header-cell *matHeaderCellDef class="action-header w-10"> 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 license"
+                   (click)="deleteVolumeLicense(element)"></fa-icon>
+        </div>
+      </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="vlColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: vlColumns;"></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/volume-license-overview/volume-license-overview.scss	(deleted)
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.scss	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.scss	(working copy)
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-overview.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-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/volume-license-overview/volume-license-overview.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.spec.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.spec.ts	(nonexistent)
@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { VolumeLicenseOverview } from './volume-license-overview';
-
-describe('VolumeLicenseOverview', () => {
-  let component: VolumeLicenseOverview;
-  let fixture: ComponentFixture<VolumeLicenseOverview>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [VolumeLicenseOverview]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(VolumeLicenseOverview);
-    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/volume-license-overview/volume-license-overview.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-overview.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VolumeLicenseOverview } from './volume-license-overview';
+
+describe('VolumeLicenseOverview', () => {
+  let component: VolumeLicenseOverview;
+  let fixture: ComponentFixture<VolumeLicenseOverview>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [VolumeLicenseOverview]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(VolumeLicenseOverview);
+    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/volume-license-overview/volume-license-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.ts	(nonexistent)
@@ -1,11 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'app-volume-license-overview',
-  imports: [],
-  templateUrl: './volume-license-overview.html',
-  styleUrl: './volume-license-overview.scss'
-})
-export class VolumeLicenseOverview {
-
-}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-overview.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/volume-license-overview/volume-license-overview.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/volume-license-overview.ts	(working copy)
@@ -0,0 +1,228 @@
+import {AfterViewInit, ChangeDetectorRef, Component, inject, OnInit, ViewChild} from '@angular/core';
+import {SharedModule} from '../../shared/shared-module';
+import {take} from 'rxjs/operators';
+import {VolumeLicense} from '../../services/volume-license';
+import {NotificationService} from '../../services/notification';
+import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material/dialog';
+import {YyyymmddPipe} from '../../pipes/yyyymmdd-pipe';
+import {MatTableDataSource} from '@angular/material/table';
+import {DatePipe} from '@angular/common';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {Confirmation} from '../../services/confirmation';
+import {MatPaginator} from '@angular/material/paginator';
+
+@Component({
+  selector: 'app-volume-license-overview',
+  standalone: true,
+  imports: [SharedModule, YyyymmddPipe],
+  templateUrl: './volume-license-overview.html',
+  styleUrl: './volume-license-overview.scss',
+  providers: [DatePipe],
+})
+export class VolumeLicenseOverview implements AfterViewInit {
+
+  vlColumns: string[] = ['serial', 'name', 'gDate', 'eDate', 'bwLimit', 'availableBandwidth', 'usedBandwidth', 'capacity', 'memoryControl', 'action'];
+  vlDataSource: MatTableDataSource<any> = new MatTableDataSource();
+
+  dialog = inject(MatDialog);
+  dialogConfig = new MatDialogConfig();
+
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
+  totalRecords: number = 0;
+
+  constructor(
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+    private _date: YyyymmddPipe,
+    private _datePipe: DatePipe,
+    private _confirmation: Confirmation,
+    private _cdRef: ChangeDetectorRef,
+  ) {
+    this.getVolumeLicenses();
+  }
+
+  ngAfterViewInit() {
+    this.vlDataSource.paginator = this.paginator;
+    this._cdRef.detectChanges();
+  }
+
+  getVolumeLicenses() {
+    this.vlDataSource.data = [];
+    this._volume.getVolumeLicenses().pipe(take(1)).subscribe({
+      next: (result: any) => {
+        this.vlDataSource.data = result;
+        this.vlDataSource.paginator = this.paginator;
+        this.totalRecords = result.length;
+      },
+      error: error => {
+        this._notification.showError(`Error: ${error?.message}`);
+      }
+    })
+  }
+
+  addVolumeLicense() {
+    this.dialogConfig.width = '45%';
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(AddVolumeLicenseDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((isAdded: boolean) => {
+      if (isAdded) {
+        this.getVolumeLicenses();
+      }
+    })
+  }
+
+  deleteVolumeLicense(_license: any) {
+    let confirmMsg = `Are you sure you want to delete the volume license "${_license?.name}"?`
+    this._confirmation.openConfirmDialog({
+      title: `Delete volume license - ${_license?.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({
+          name: _license?.name,
+        }));
+        this._volume.deleteVolumeLicense(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(`${_license?.name} deleted successfully!`);
+                  this.getVolumeLicenses()
+                }
+              }
+            },
+            error: (err: any) => {
+              this._notification.showError('Failed to delete the volume license.');
+            }
+          })
+      } else {
+        this._notification.showSuccess('Delete cancelled.');
+      }
+    })
+  }
+
+  goToDetails(_license: any) {
+    this.dialogConfig.data = {
+      name: _license.name,
+      dataSource: [
+        {key: 'Name', value: _license.name,},
+        {key: 'License key', value: _license.license},
+        {
+          key: 'Generation Date',
+          value: this._datePipe.transform(this._date.transform(_license.generation_date), 'MMMM d, y')
+        },
+        {
+          key: 'Expiration Date',
+          value: this._datePipe.transform(this._date.transform(_license.expiration_date), 'MMMM d, y')
+        },
+        {key: 'Bandwidth Limitation', value: _license.bandwidth},
+        {key: 'Available Bandwidth', value: `${_license.available_bw} Mbps`},
+        {key: 'Allocated Bandwidth', value: `${_license.used_bw} Mpbs`},
+        {key: 'vAPV Capacity', value: _license.capacity},
+        {key: 'Memory Control', value: _license.memory_control},
+      ]
+    };
+    this.dialogConfig.width = '45%';
+    this.dialogConfig.disableClose = true;
+    const dialogRef = this.dialog.open(ShowVolumeLicenseDetailsDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe(() => {
+    })
+  }
+}
+
+@Component({
+  selector: 'show-volume-license-details',
+  templateUrl: './show-volume-license-details.html',
+  imports: [SharedModule]
+})
+export class ShowVolumeLicenseDetailsDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<ShowVolumeLicenseDetailsDialog>);
+
+  licenseDataSource = new MatTableDataSource();
+  licenseColumns: Array<string> = ['key', 'value'];
+
+  constructor() {
+  }
+
+  ngOnInit() {
+    this.licenseDataSource = new MatTableDataSource(this.data?.dataSource);
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+}
+
+
+@Component({
+  selector: 'add-volume-license',
+  templateUrl: './add-volume-license.html',
+  imports: [SharedModule]
+})
+export class AddVolumeLicenseDialog implements OnInit {
+
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<AddVolumeLicenseDialog>);
+
+  vlForm: FormGroup;
+
+  constructor(
+    private _formBuilder: FormBuilder,
+    private _volume: VolumeLicense,
+    private _notification: NotificationService,
+  ) {
+    this.vlForm = this._formBuilder.group({
+      name: ['', Validators.required],
+      license: ['', Validators.required],
+    });
+  }
+
+  ngOnInit() {
+
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  onSubmit() {
+    if (this.vlForm.invalid) {
+      console.log(this.vlForm.value);
+      this.vlForm.markAllAsTouched();
+      return;
+    }
+
+    let rawPayload = new FormData();
+    rawPayload.set('post_data', JSON.stringify({
+      name: this.vlForm.value?.name,
+      license: this.vlForm.value?.license,
+    }));
+    this._volume.addVolumeLicense(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(`Volume license successfully!`);
+              this.dialogRef.close(true);
+            }
+          }
+        },
+        error: (err: any) => {
+          this._notification.showError('Failed to add volume license.');
+        }
+      })
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/tasks/tasks.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/tasks/tasks.html	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/tasks/tasks.html	(working copy)
@@ -4,7 +4,8 @@
   </mat-card-header>
   <mat-card-content>
     <div class="button-container">
-      <button mat-raised-button (click)="deleteAllTasks()" class="warning" matTooltip="Delete All Tasks">Delete All</button>
+      <button mat-raised-button (click)="deleteAllTasks()" class="warning" matTooltip="Delete All Tasks">Delete All
+      </button>
     </div>
     <div class="table-container">
       <div class="mat-elevation-z0">
@@ -64,7 +65,7 @@
         <mat-paginator
           [length]="totalResults"
           [pageSize]="10"
-          [pageSizeOptions]="[5, 10, 20, 50]"
+          [pageSizeOptions]="[10, 20, 50]"
         >
         </mat-paginator>
       </div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/tasks/tasks.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/tasks/tasks.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/tasks/tasks.ts	(working copy)
@@ -1,4 +1,4 @@
-import {ChangeDetectorRef, Component, OnInit} from '@angular/core';
+import {AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
 import {SystemService} from '../../services/system-service';
 import {NotificationService} from '../../services/notification';
 import {SharedModule} from '../../shared/shared-module';
@@ -7,6 +7,7 @@
 import {TaskTimeFromNamePipe} from '../../pipes/task-time-from-name-pipe';
 import {Confirmation} from '../../services/confirmation';
 import {Subject} from 'rxjs';
+import {MatPaginator} from '@angular/material/paginator';
 
 @Component({
   selector: 'app-tasks',
@@ -14,13 +15,13 @@
   templateUrl: './tasks.html',
   styleUrl: './tasks.scss'
 })
-export class Tasks implements OnInit {
+export class Tasks implements OnInit, AfterViewInit {
 
   tasksColumns = ['serial', 'name', 'device', 'type', 'created_at', 'executed_at', 'status', 'action'];
   dataSource = new MatTableDataSource([]);
   totalResults = 0;
+  @ViewChild(MatPaginator) paginator!: MatPaginator;
 
-
   constructor(
     private systemService: SystemService,
     private notification: NotificationService,
@@ -35,17 +36,21 @@
     })
   }
 
+  ngAfterViewInit() {
+    this.dataSource.paginator = this.paginator;
+    this.cdRef.detectChanges();
+  }
+
   getTasks(payload: any) {
-    this.dataSource = new MatTableDataSource();
+    this.dataSource.data = [];
     let _payload = new FormData();
     _payload.set('post_data', JSON.stringify(payload));
     this.systemService.getTasks(_payload)
       .pipe(take(1))
       .subscribe({
         next: (result: any) => {
-          console.log(result);
           if (result?.task) {
-            this.dataSource = new MatTableDataSource(result.task?.data);
+            this.dataSource.data = result.task?.data;
             this.totalResults = result.task?.total;
           }
           this.startPolling();
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.scss	(added)
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.scss	(revision 0)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-devices-overview/volume-license-devices-overview.scss	(revision 0)
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/add-volume-license.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/add-volume-license.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/add-volume-license.html	(working copy)
@@ -0,0 +1,64 @@
+<h2 mat-dialog-title>Add Volume License</h2>
+<mat-dialog-content>
+  <form
+    (ngSubmit)="onSubmit()"
+    [formGroup]="vlForm"
+  >
+    <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 (vlForm.get('name')?.invalid && vlForm.get('name')?.touched) {
+          <mat-error>
+            @if (vlForm.get('name')?.errors?.['required']) {
+              Name is required.
+            } @else if (vlForm.get('name')?.errors) {
+              Invalid name format.
+            }
+          </mat-error>
+        }
+      </mat-form-field>
+    </div>
+    <div class="form-field-wrapper">
+      <label for="license" class="form-label">License Key *</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <input
+          id="license"
+          formControlName="license"
+          matInput
+          placeholder="License Key"
+          type="text"
+        />
+        @if (vlForm.get('license')?.invalid && vlForm.get('license')?.touched) {
+          <mat-error>
+            @if (vlForm.get('license')?.errors?.['required']) {
+              License key is required.
+            } @else if (vlForm.get('license')?.errors) {
+              Invalid license 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/volume-license-overview/show-volume-license-details.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/show-volume-license-details.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/volume-license-overview/show-volume-license-details.html	(working copy)
@@ -0,0 +1,20 @@
+<h2 mat-dialog-title>Volume license - {{ this.data?.name }}</h2>
+<mat-dialog-content>
+  <table mat-table [dataSource]="licenseDataSource" class="mat-elevation-z1">
+    <ng-container matColumnDef="key">
+      <td mat-cell *matCellDef="let element"> {{ element.key }}</td>
+    </ng-container>
+    <ng-container matColumnDef="value">
+      <td mat-cell *matCellDef="let element"> {{ element.value }}</td>
+    </ng-container>
+    <tr mat-row *matRowDef="let row; columns: licenseColumns;"></tr>
+  </table>
+</mat-dialog-content>
+<mat-dialog-actions>
+  <button
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+</mat-dialog-actions>
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 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/api_urls.ts	(working copy)
@@ -86,5 +86,21 @@
   DELETE_USER_URL: `${PREFIX}/api/cm/system/user_mgmt/Administrator/_delete`,
   ADD_USER_URL: `${PREFIX}/api/cm/system/user_mgmt/Administrator/_add`,
   UPDATE_USER_URL: `${PREFIX}/api/cm/system/user_mgmt/Administrator/_update/username`,
-
+  GET_VOLUME_LICENSES_URL: `${PREFIX}/api/cm/device_mgmt/license/VolumeLicense/_get_list_data`,
+  ADD_VOLUME_LICENSE_URL: `${PREFIX}/api/cm/device_mgmt/license/VolumeLicense/_add`,
+  DELETE_VOLUME_LICENSE_URL: `${PREFIX}/api/cm/device_mgmt/license/VolumeLicense/_delete`,
+  GET_VL_DEVICES_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_get_list_data`,
+  GET_AMP_DEVICES_LIST_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_get_list_data`,
+  DEACTIVATE_DEVICE_VL_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_perform?action=Deactivate`,
+  ACTIVATE_DEVICE_VL_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_perform?action=Activate`,
+  REMOVE_VL_MANAGED_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_delete`,
+  IMPORT_DEVICE_TO_VL_MANAGED_DEVICES_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_perform`,
+  ADD_VL_MANAGED_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_add`,
+  UPDATE_VL_DEVICE_BANDWIDTH_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/CMDevice/_update/id/`,
+  GET_VL_DEVICE_VERSION_URL: `${PREFIX}cm/volume_license/device_version`,
+  GET_VL_DISCOVER_DEVICES_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/DiscoverDevice/_get_list_data`,
+  GET_VL_DISCOVER_DEVICES_MODE_URL: `${PREFIX}/cm/volume_license/get_enable_mode`,
+  ACTIVATE_VL_DISCOVER_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/DiscoverDevice/_perform?action=Activate`,
+  UPDATE_VL_DISCOVER_DEVICES_URL: `${PREFIX}/cm/volume_license/set_enable_mode`,
+  DELETE_VL_DISCOVER_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/cm_device/DiscoverDevice/_delete`,
 } 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 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/menu.ts	(working copy)
@@ -100,6 +100,27 @@
     ]
   },
   {
+    label: 'Volume License',
+    icon: '',
+    expanded: false,
+    children: [
+      {
+        label: 'Licenses',
+        icon: '',
+        routerLink: '/vl/licenses',
+        roles: ['super_admin', 'device_admin', 'common_admin'],
+        permissions: ['vlLicenses']
+      },
+      {
+        label: 'Devices',
+        icon: '',
+        routerLink: '/vl/devices',
+        roles: ['super_admin', 'device_admin', 'common_admin'],
+        permissions: ['vlDevices']
+      },
+    ]
+  },
+  {
     label: 'Upgrade Centre',
     icon: '',
     routerLink: '/upgrade-centre',
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/newline-to-br-pipe.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/newline-to-br-pipe.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/newline-to-br-pipe.spec.ts	(working copy)
@@ -0,0 +1,8 @@
+import { NewlineToBrPipe } from './newline-to-br-pipe';
+
+describe('NewlineToBrPipe', () => {
+  it('create an instance', () => {
+    const pipe = new NewlineToBrPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/newline-to-br-pipe.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/newline-to-br-pipe.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/newline-to-br-pipe.ts	(working copy)
@@ -0,0 +1,19 @@
+import {Pipe, PipeTransform} from '@angular/core';
+import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
+
+@Pipe({
+  name: 'newlineToBr'
+})
+export class NewlineToBrPipe implements PipeTransform {
+
+  constructor(private sanitizer: DomSanitizer) {
+  }
+
+  transform(value: string | null | undefined): SafeHtml {
+    if (value === null || value === undefined) {
+      return '';
+    }
+    const transformedValue = value.replace(/\n/g, '<br>');
+    return this.sanitizer.bypassSecurityTrustHtml(transformedValue);
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/yyyymmdd-pipe.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/yyyymmdd-pipe.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/yyyymmdd-pipe.spec.ts	(working copy)
@@ -0,0 +1,8 @@
+import { YyyymmddPipe } from './yyyymmdd-pipe';
+
+describe('YyyymmddPipe', () => {
+  it('create an instance', () => {
+    const pipe = new YyyymmddPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/yyyymmdd-pipe.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/yyyymmdd-pipe.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/pipes/yyyymmdd-pipe.ts	(working copy)
@@ -0,0 +1,46 @@
+import {Injectable, Pipe, PipeTransform} from '@angular/core';
+
+@Pipe({
+  name: 'yyyymmdd',
+  standalone: true,
+})
+@Injectable({ providedIn: 'root' })
+export class YyyymmddPipe implements PipeTransform {
+
+  transform(value: string | number | Date | null | undefined): Date | null {
+    if (value === null || typeof value === 'undefined' || value === '') {
+      return null;
+    }
+
+    if (value instanceof Date) {
+      return value;
+    }
+
+    let dateString: string;
+
+    if (typeof value === 'number') {
+      dateString = String(value);
+    } else {
+      dateString = value;
+    }
+
+    if (dateString.length !== 8 || !/^\d{8}$/.test(dateString)) {
+      return null;
+    }
+
+    const year = dateString.substring(0, 4);
+    const month = dateString.substring(4, 6);
+    const day = dateString.substring(6, 8);
+
+    const isoFormattedString = `${year}-${month}-${day}`;
+
+    const parsedDate = new Date(isoFormattedString);
+
+    if (isNaN(parsedDate.getTime())) {
+      return null;
+    }
+
+    return parsedDate;
+  }
+
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/utils-service.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/utils-service.ts	(revision 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/utils-service.ts	(working copy)
@@ -33,7 +33,8 @@
       'vpn_num': 'number of licensed vpn devices',
       'vpn_lic_sess': 'number of licensed vpn sessions',
       'waf_num': 'number of licensed waf devices',
-      "expiration_data": "expiration_date",
+      'expiration_data': "expiration_date",
+      'num': 'Number'
     };
 
     let processedKey = key;
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/volume-license.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/volume-license.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/volume-license.spec.ts	(working copy)
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { VolumeLicense } from './volume-license';
+
+describe('VolumeLicense', () => {
+  let service: VolumeLicense;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(VolumeLicense);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/volume-license.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/volume-license.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/volume-license.ts	(working copy)
@@ -0,0 +1,126 @@
+import {Injectable} from '@angular/core';
+import {HttpService} from './http';
+import {URLS} from '../constants/api_urls';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class VolumeLicense {
+
+  constructor(private http: HttpService) {
+  }
+
+  getVolumeLicenses() {
+    return this.http.get(URLS.GET_VOLUME_LICENSES_URL);
+  }
+
+  addVolumeLicense(rawPayload: any) {
+    return this.http.post(URLS.ADD_VOLUME_LICENSE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  deleteVolumeLicense(rawPayload: any) {
+    return this.http.post(URLS.DELETE_VOLUME_LICENSE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  getManagedDevices() {
+    return this.http.get(URLS.GET_VL_DEVICES_URL);
+  }
+
+  activateDeviceVolumeLicense(rawPayload: any) {
+    return this.http.post(URLS.ACTIVATE_DEVICE_VL_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  deactivateDeviceVolumeLicense(rawPayload: any) {
+    return this.http.post(URLS.DEACTIVATE_DEVICE_VL_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  removeVLManagedDevice(rawPayload: any) {
+    return this.http.post(URLS.REMOVE_VL_MANAGED_DEVICE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  importVLManagedDevice(rawPayload: any) {
+    return this.http.post(URLS.IMPORT_DEVICE_TO_VL_MANAGED_DEVICES_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  addVLManagedDevice(rawPayload: any) {
+    return this.http.post(URLS.ADD_VL_MANAGED_DEVICE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  updateVLManagedDeviceBandwidth(deviceId: any, rawPayload: any) {
+    let baseUrl = URLS.UPDATE_VL_DEVICE_BANDWIDTH_URL;
+    let finalUrl = `${baseUrl}/%22${deviceId}%22`
+    return this.http.post(finalUrl, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  getVLManagedDeviceVersion(rawPayload: any) {
+    return this.http.post(URLS.GET_VL_DEVICE_VERSION_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  getVLDiscoverDevices() {
+    return this.http.get(URLS.GET_VL_DISCOVER_DEVICES_URL);
+  }
+
+  getVLDiscoverDevicesMode() {
+    return this.http.get(URLS.GET_VL_DISCOVER_DEVICES_MODE_URL);
+  }
+
+  updateVLDiscoverDeviceMode(rawPayload: any) {
+    return this.http.post(URLS.UPDATE_VL_DISCOVER_DEVICES_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  deleteVLDiscoverDevice(rawPayload: any) {
+    return this.http.post(URLS.DELETE_VL_DISCOVER_DEVICE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  activateVLDiscoverDevice(rawPayload: any) {
+    return this.http.post(URLS.ACTIVATE_VL_DISCOVER_DEVICE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+}
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 2668)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/shared/shared-module.ts	(working copy)
@@ -42,6 +42,8 @@
   faEdit,
   faXmarkCircle,
   faBuilding,
+  faPlayCircle,
+  faCircleStop,
 } from '@fortawesome/free-regular-svg-icons';
 import {MatGridListModule} from '@angular/material/grid-list';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -92,7 +94,8 @@
     MatDatepickerModule,
     MatTimepickerModule,
     MatPaginatorModule,
-    NgxEchartsModule
+    NgxEchartsModule,
+    MatExpansionModule,
   ],
   exports: [
     CommonModule,
@@ -120,6 +123,7 @@
     MatTimepickerModule,
     MatPaginatorModule,
     NgxEchartsModule,
+    MatExpansionModule,
   ]
 })
 export class SharedModule {
@@ -161,7 +165,9 @@
       faEdit,
       faXmarkCircle,
       faServer,
-      faBuilding
+      faBuilding,
+      faPlayCircle,
+      faCircleStop,
     );
   }
 }
