Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/package.json
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/package.json	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/package.json	(working copy)
@@ -28,7 +28,9 @@
     "express": "^5.1.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
-    "zone.js": "~0.15.0"
+    "zone.js": "~0.15.0",
+    "xterm": "^5.3.0",
+    "xterm-addon-fit": "^0.8.0"
   },
   "devDependencies": {
     "@angular/build": "^20.0.3",
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.html	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.html	(working copy)
@@ -1,12 +1,14 @@
-<div class="app-wrapper" [class.sidebar-collapsed]="isSidebarCollapsed">
-  @if (currentUser) {
+<div class="app-wrapper" [class.sidebar-collapsed]="isSidebarCollapsed && showNavigation">
+  @if (currentUser && showNavigation) {
     <app-navigation></app-navigation>
   }
-  <div class="main-content" [class.no-navigation-margin]="!currentUser"
+  <div class="main-content"
+       [class.no-navigation-margin]="!currentUser && showNavigation"
        [ngStyle]="{
-    marginLeft: isSidebarCollapsed ? '60px' : '250px',
-    width: isSidebarCollapsed ? 'calc(100% - 60px)' : 'calc(100% - 250px)'
-  }">
+         marginLeft: showNavigation ? (isSidebarCollapsed ? '60px' : '250px') : '0px',
+         width: showNavigation ? (isSidebarCollapsed ? 'calc(100% - 60px)' : 'calc(100% - 250px)') : '100%',
+         marginTop: showNavigation ? (isSidebarCollapsed ? '45px' : '45px') : '0px',
+       }">
     <div class="main-content-inner">
       @if (loading$ | async) {
         <app-loading></app-loading>
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 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.routes.ts	(working copy)
@@ -5,6 +5,8 @@
 import {GeneralSettings} from './components/general-settings/general-settings';
 import {DeviceGroups} from './components/sub-components/device-groups/device-groups';
 import {Devices} from './components/sub-components/devices/devices';
+import {DeviceDetails} from './components/sub-components/device-details/device-details';
+import {WebConsole} from './components/sub-components/web-console/web-console';
 
 
 export const routes: Routes = [
@@ -22,6 +24,16 @@
             component: Devices,
             data: {roles: ['super_admin', 'device_admin', 'common_admin']}
           },
+          {
+            path: 'devices/:deviceName',
+            component: DeviceDetails,
+            data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+          },
+          {
+            path: 'web-console',
+            component: WebConsole,
+            data: {roles: ['super_admin', 'device_admin', 'common_admin']}
+          },
         ]
       },
       {
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.scss	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.scss	(working copy)
@@ -10,8 +10,6 @@
 .main-content {
   flex-grow: 1;
   transition: margin-left 0.3s ease, margin-top 0.3s ease;
-  margin-top: 45px;
-  margin-left: 250px;
   width: calc(100% - 250px);
   box-sizing: border-box;
   overflow-y: auto; /* Allow vertical scroll only if needed */
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.ts	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/app.ts	(working copy)
@@ -1,7 +1,8 @@
 import {Component, OnDestroy, OnInit} from '@angular/core';
-import {RouterOutlet} from '@angular/router';
+import {Router, RouterOutlet, NavigationEnd} from '@angular/router';
 import {Auth} from './services/auth';
 import {Subscription} from 'rxjs';
+import {filter} from 'rxjs/operators';
 import {Navigation} from './components/navigation/navigation';
 import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
 import {User} from './models/user';
@@ -23,28 +24,47 @@
   protected title = 'Array Management Platform';
   private authSubscription!: Subscription;
 
-  constructor(private authService: Auth, private layoutService: Layout, private loadingService: LoadingService) {
-    this.loading$ = this.loadingService.loading$
+  showNavigation: boolean = true;
+
+  constructor(
+    private authService: Auth,
+    private layoutService: Layout,
+    private loadingService: LoadingService,
+    private router: Router
+  ) {
+    this.loading$ = this.loadingService.loading$;
   }
 
-
   ngOnInit(): void {
     this.authSubscription = this.authService.currentUser.subscribe(user => {
       this.currentUser = user;
+      this.checkNavigationVisibility(this.router.url);
     });
 
     this.layoutService.getSidebarCollapsed().subscribe(
       (state) => this.isSidebarCollapsed = state
     );
+
+    this.router.events.pipe(
+      filter(event => event instanceof NavigationEnd)
+    ).subscribe((event: NavigationEnd) => {
+      this.checkNavigationVisibility(event.urlAfterRedirects);
+    });
+
+    this.checkNavigationVisibility(this.router.url);
   }
 
   ngOnDestroy(): void {
-    // Unsubscribe to prevent memory leaks
     if (this.authSubscription) {
       this.authSubscription.unsubscribe();
     }
   }
 
+  private checkNavigationVisibility(url: string): void {
+    const webConsoleStandalonePath = '/inventory/web-console';
+    this.showNavigation = this.currentUser !== null && !url.includes(webConsoleStandalonePath);
+  }
+
   onSidebarToggle(collapsed: boolean): void {
     this.isSidebarCollapsed = collapsed;
   }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.html	(working copy)
@@ -0,0 +1,247 @@
+<mat-card class="page-card" appearance="filled">
+  <mat-card-header>
+    <mat-card-title>
+      <a class="back-to-main-page" (click)="backToManagedDevices()">
+        <fa-icon [icon]="['far', 'circle-left']"></fa-icon> Managed Devices - {{deviceData?.name}}
+      </a>
+    </mat-card-title>
+  </mat-card-header>
+</mat-card>
+<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start">
+  <mat-tab label="Basic Information">
+    <mat-card class="page-card-1" appearance="filled">
+      <mat-card-header>
+        <mat-card-title>Device Switch</mat-card-title>
+      </mat-card-header>
+      <mat-card-content>
+        <mat-slide-toggle labelPosition="before" [(ngModel)]="isMonitorEnabled"
+                          (change)="onMonitorToggleChange($event)">Enable Monitor
+        </mat-slide-toggle>
+      </mat-card-content>
+    </mat-card>
+    <mat-card class="page-card-1" appearance="filled">
+      <mat-card-header>
+        <mat-card-title>License Information</mat-card-title>
+      </mat-card-header>
+      <mat-card-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-card-content>
+    </mat-card>
+    <mat-card class="page-card-1" appearance="filled">
+      <mat-card-header>
+        <mat-card-title>System Information</mat-card-title>
+      </mat-card-header>
+      <mat-card-content>
+        <table mat-table [dataSource]="systemDataSource" 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: systemColumns;"></tr>
+        </table>
+      </mat-card-content>
+    </mat-card>
+  </mat-tab>
+  <mat-tab label="Configuration">
+    @if (deviceConfig == null) {
+      <p> Failed to load the device configurations.</p>
+    } @else {
+      <div class="device-config">
+        <button mat-raised-button (click)="downloadConfigFile()">Download Config</button>
+        <pre class="wrapped-pre">{{deviceConfig}}</pre>
+      </div>
+    }
+  </mat-tab>
+  <mat-tab label="Settings">
+    <form
+      (ngSubmit)="updateDeviceSettings()"
+      [formGroup]="deviceSettingsForm"
+      class="device-settings-form"
+    >
+      <div class="form-field-wrapper">
+        <label for="portNumber" class="form-label">API Port *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="portNumber"
+            formControlName="portNumber"
+            matInput
+            placeholder="9997"
+            type="number"
+          />
+          @if (deviceSettingsForm.get('portNumber')?.invalid && deviceSettingsForm.get('portNumber')?.touched) {
+            <mat-error>
+              @if (deviceSettingsForm.get('portNumber')?.errors?.['required']) {
+                API port is required.
+              } @else {
+                Invalid port number format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="apiUsername" class="form-label">API Username *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="apiUsername"
+            formControlName="apiUsername"
+            matInput
+            placeholder="API Username"
+            type="text"
+          />
+          @if (deviceSettingsForm.get('apiUsername')?.invalid && deviceSettingsForm.get('apiUsername')?.touched) {
+            <mat-error>
+              @if (deviceSettingsForm.get('apiUsername')?.errors?.['required']) {
+                Username is required.
+              } @else {
+                Invalid username format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="apiPassword" class="form-label">API Password *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="apiPassword"
+            formControlName="apiPassword"
+            matInput
+            placeholder="API Password"
+            type="password"
+          />
+          @if (deviceSettingsForm.get('apiPassword')?.invalid && deviceSettingsForm.get('apiPassword')?.touched) {
+            <mat-error>
+              @if (deviceSettingsForm.get('apiPassword')?.errors?.['required']) {
+                Password is required.
+              } @else {
+                Invalid password format.
+              }
+            </mat-error>
+          }
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="webuiPortNumber" class="form-label">WebUI Port *</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input id="webuiPortNumber" formControlName="webuiPortNumber" matInput placeholder="8888" type="number"/>
+        </mat-form-field>
+      </div>
+      @if (!['AG', 'vxAG'].includes(deviceSettingsForm.get('deviceType')?.value)) {
+        <div class="form-field-wrapper">
+          <label for="webuiUsername" class="form-label">WebUI Username</label>
+          <mat-form-field appearance="outline" subscriptSizing="dynamic">
+            <input
+              id="webuiUsername"
+              formControlName="webuiUsername"
+              matInput
+              placeholder="WebUI Username"
+              type="text"
+            />
+            @if (deviceSettingsForm.get('webuiUsername')?.invalid && deviceSettingsForm.get('webuiUsername')?.touched) {
+              <mat-error>
+                @if (deviceSettingsForm.get('webuiUsername')?.errors?.['required']) {
+                  Username is required.
+                } @else {
+                  Invalid username format.
+                }
+              </mat-error>
+            }
+          </mat-form-field>
+        </div>
+        <div class="form-field-wrapper">
+          <label for="webuiPassword" class="form-label">WebUI Password *</label>
+          <mat-form-field appearance="outline" subscriptSizing="dynamic">
+            <input
+              id="webuiPassword"
+              formControlName="webuiPassword"
+              matInput
+              placeholder="WebUI Password"
+              type="password"
+            />
+            @if (deviceSettingsForm.get('webuiPassword')?.invalid && deviceSettingsForm.get('webuiPassword')?.touched) {
+              <mat-error>
+                @if (deviceSettingsForm.get('webuiPassword')?.errors?.['required']) {
+                  Password is required.
+                } @else {
+                  Invalid password format.
+                }
+              </mat-error>
+            }
+          </mat-form-field>
+        </div>
+      }
+      <div class="form-field-wrapper">
+        <label for="enablePassword" class="form-label">Enable Password</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input id="enablePassword" formControlName="enablePassword" matInput placeholder="Password" type="password"/>
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="gatewayDomain" class="form-label">Gateway Domain</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="gatewayDomain"
+            formControlName="gatewayDomain"
+            matInput
+            placeholder="Gateway Domain"
+            type="text"
+          />
+        </mat-form-field>
+      </div>
+      <div class="form-field-wrapper">
+        <label for="location" class="form-label">Location</label>
+        <mat-form-field appearance="outline" subscriptSizing="dynamic">
+          <input
+            id="location"
+            formControlName="location"
+            matInput
+            placeholder="Location"
+            type="text"
+          />
+        </mat-form-field>
+      </div>
+      @if (deviceSettingsForm.get('deviceGroupSource')?.value === 'created') {
+        <div class="form-field-wrapper">
+          <label for="groupName" class="form-label">Device Type</label>
+          <mat-form-field appearance="outline" subscriptSizing="dynamic">
+            <mat-select formControlName="groupName">
+              @for (type of groups; track type) {
+                <mat-option [value]="type">{{ type }}</mat-option>
+              }
+            </mat-select>
+            @if (deviceSettingsForm.get('groupName')?.invalid && deviceSettingsForm.get('groupName')?.touched) {
+              <mat-error>
+                @if (deviceSettingsForm.get('groupName')?.errors?.['required']) {
+                  Group name is required.
+                } @else {
+                  Invalid group name format.
+                }
+              </mat-error>
+            }
+          </mat-form-field>
+        </div>
+      }
+      <div class="form-field-wrapper">
+        <label for="enableLog" class="form-label">Enable Log</label>
+        <mat-slide-toggle formControlName="enableLog"></mat-slide-toggle>
+      </div>
+      <button
+        type="submit"
+        mat-raised-button
+        color="primary">
+        Submit
+      </button>
+    </form>
+  </mat-tab>
+</mat-tab-group>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.scss	(working copy)
@@ -0,0 +1,79 @@
+.page-card {
+  width: 100%;
+  border-radius: 0;
+  background-color: inherit;
+  font-size: 14px !important;
+}
+
+.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;
+}
+
+.device-settings-form {
+  padding: 20px;
+  width: 100%;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .form-field-wrapper {
+    display: flex; /* Use flexbox to align items horizontally */
+    align-items: center; /* Vertically align items in the middle */
+    gap: 15px; /* Add some space between the label and the form field */
+    margin-bottom: 20px; /* Space between rows if you have multiple fields */
+  }
+
+  .form-label {
+    font-weight: 500;
+    min-width: 150px;
+    text-align: right;
+  }
+
+  .form-field-wrapper mat-form-field {
+    flex-grow: 1;
+    min-width: 300px;
+    max-width: 600px;
+  }
+}
+
+.device-config {
+  padding: 20px;
+  //background-color: #f9f9f9;
+  border-radius: 8px;
+  //box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+  max-width: 90vw;
+  margin: 20px auto;
+}
+
+.device-config button {
+  margin-bottom: 15px;
+}
+
+.wrapped-pre {
+  //background-color: #eef;
+  padding: 15px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  overflow-x: hidden;
+  white-space: pre-wrap;
+  word-break: break-word;
+  font-family: monospace;
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DeviceDetails } from './device-details';
+
+describe('DeviceDetails', () => {
+  let component: DeviceDetails;
+  let fixture: ComponentFixture<DeviceDetails>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [DeviceDetails]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(DeviceDetails);
+    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/device-details/device-details.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/device-details/device-details.ts	(working copy)
@@ -0,0 +1,272 @@
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute, Router} from '@angular/router';
+import {SharedModule} from '../../../shared/shared-module';
+import {MatTab, MatTabGroup} from '@angular/material/tabs';
+import {MatTableDataSource} from '@angular/material/table';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {take} from 'rxjs/operators';
+import {NotificationService} from '../../../services/notification';
+import {DeviceService} from '../../../services/device-service';
+
+export interface KeyValuePair {
+  key: string;
+  value: any;
+}
+
+@Component({
+  selector: 'app-device-details',
+  imports: [SharedModule, MatTabGroup, MatTab,],
+  templateUrl: './device-details.html',
+  styleUrl: './device-details.scss'
+})
+export class DeviceDetails implements OnInit {
+  deviceName: string | null = null;
+  deviceData: any = null;
+  groups: any = [];
+
+  licenseDataSource = new MatTableDataSource<KeyValuePair>();
+  licenseColumns: Array<string> = ['key', 'value'];
+
+  systemDataSource = new MatTableDataSource<KeyValuePair>();
+  systemColumns: Array<string> = ['key', 'value'];
+
+  isMonitorEnabled: boolean = false;
+
+  deviceConfig: any = null;
+
+  deviceSettingsForm: FormGroup;
+
+  constructor(
+    private route: ActivatedRoute,
+    private router: Router,
+    private deviceService: DeviceService,
+    private notification: NotificationService,
+    private formBuilder: FormBuilder) {
+    this.deviceSettingsForm = this.formBuilder.group({
+      portNumber: ['9997', [Validators.required, Validators.min(0), Validators.max(65535)]],
+      apiUsername: ['', Validators.required],
+      apiPassword: ['', Validators.required],
+      gatewayDomain: ['',],
+      location: ['',],
+      webuiUsername: ['', Validators.required],
+      webuiPassword: ['', Validators.required],
+      webuiPortNumber: ['8888', Validators.required],
+      deviceGroupSource: ['created', Validators.required],
+      groupName: ['', Validators.required],
+      enablePassword: [''],
+      enableLog: [true],
+    });
+  }
+
+  ngOnInit(): void {
+    this.deviceName = this.route.snapshot.paramMap.get('deviceName');
+    this.deviceData = history.state.deviceData;
+    this.groups = history.state.groups;
+    let licenseData: any = {};
+    let systemData: any = {};
+
+    if (this.deviceData) {
+      if (typeof this.deviceData === 'string') {
+        try {
+          this.deviceData = JSON.parse(this.deviceData);
+        } catch (error) {
+          console.error('Error parsing deviceData:', error);
+          this.deviceData = null;
+        }
+      }
+    }
+    console.log(this.deviceData);
+    Object.keys(this.deviceData).forEach(key => {
+      const value = this.deviceData[key as keyof typeof this.deviceData];
+      if (['license_date', 'license_feature', 'license_key', 'license_limit'].includes(key)) {
+        licenseData[key] = value;
+      } else if (['host_name', 'model', 'serial_number', 'build_version', 'system_cpu', 'system_ram', 'system_boot_time', 'system_up_time', 'platform_bld_date', 'ssl_hw'].includes(key)) {
+        systemData[key] = value;
+      }
+    });
+    this.isMonitorEnabled = this.deviceData?.snmp_general?.snmp_enable;
+    this.licenseDataSource = this.processConfigData(licenseData);
+    this.systemDataSource = this.processConfigData(systemData);
+
+    this.deviceSettingsForm.patchValue({
+      portNumber: this.deviceData['restapi_port'],
+      apiUsername: '',
+      apiPassword: '',
+      gatewayDomain: this.deviceData['gateway_domain'],
+      location: this.deviceData['location'],
+      webuiUsername: '',
+      webuiPassword: '',
+      webuiPortNumber: this.deviceData['webui_port'],
+      groupName: this.deviceData['device_group'],
+      enablePassword: this.deviceData['enable_password'],
+      enableLog: this.deviceData['log_enable'] == 1,
+    })
+
+    const initialDeviceType = this.deviceData?.type;
+    const webuiUsernameControl = this.deviceSettingsForm.get('webuiUsername');
+    const webuiPasswordControl = this.deviceSettingsForm.get('webuiPassword');
+
+    if (webuiUsernameControl && webuiPasswordControl) {
+      if (initialDeviceType === 'AG' || initialDeviceType === 'vAG') {
+        webuiUsernameControl.clearValidators();
+        webuiPasswordControl.clearValidators();
+      } else {
+        webuiUsernameControl.setValidators(Validators.required);
+        webuiPasswordControl.setValidators(Validators.required);
+      }
+      webuiUsernameControl.updateValueAndValidity();
+      webuiPasswordControl.updateValueAndValidity();
+    }
+
+    this.getDeviceConfiguration(this.deviceData?.name);
+  }
+
+  backToManagedDevices(): void {
+    this.router.navigate(['/inventory/devices']);
+  }
+
+  processConfigData(configData: any): any {
+    let dataSource = new MatTableDataSource<KeyValuePair>();
+    if (configData) {
+      const dataArray: KeyValuePair[] = Object.keys(configData).map(key => ({
+        key: this.formatKey(key), // Format the key for display
+        value: configData[key] === "" ? "N/A" : configData[key] // Display "N/A" for empty strings
+      }));
+      dataSource.data = dataArray;
+    } else {
+      dataSource.data = [];
+    }
+    return dataSource;
+  }
+
+  formatKey(key: string): string {
+    const rawReplacements: { [abbreviation: string]: string } = {
+      'bld': 'build',
+      'hw': 'hardware',
+    };
+
+    let processedKey = key;
+    for (const abbr in rawReplacements) {
+      if (Object.prototype.hasOwnProperty.call(rawReplacements, abbr)) {
+        const pattern = new RegExp(`(^|_)${abbr}(_|$)`, 'gi');
+        processedKey = processedKey.replace(pattern, (match, p1, p2) => {
+          return p1 + rawReplacements[abbr] + p2;
+        });
+        const directPattern = new RegExp(`^${abbr}$`, 'gi');
+        processedKey = processedKey.replace(directPattern, rawReplacements[abbr]);
+      }
+    }
+
+    let formattedKey = processedKey
+      .replace(/_/g, ' ')
+      .replace(/([A-Z])/g, ' $1')
+      .replace(/^./, (str) => str.toUpperCase())
+      .replace(/\s+/g, ' ').trim();
+
+    const wordsToForceUppercase = ['cpu', 'ram', 'ssl'];
+    const uppercasePattern = new RegExp(`\\b(${wordsToForceUppercase.join('|')})\\b`, 'gi');
+    formattedKey = formattedKey.replace(uppercasePattern, (match) => match.toUpperCase());
+
+    return formattedKey;
+  }
+
+  onMonitorToggleChange(event: any): void {
+    let payload = {
+      id: this.deviceData?.id,
+      name: this.deviceData?.name,
+      type: this.deviceData?.type,
+    }
+    let rawPayload = new FormData();
+    rawPayload.set('action', event?.checked ? 'EnableMonitoring' : 'DisableMonitoring');
+    rawPayload.set('options', JSON.stringify({"__pk_list": [JSON.stringify(payload)]}));
+    this.deviceService.updateDeviceMonitoringState(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (result[0]) {
+              this.notification.showSuccess(`${this.deviceData?.name} updated successfully!`);
+              this.backToManagedDevices();
+            } else {
+              this.notification.showError(`Failed to update device: ${result[1]}`);
+            }
+          }
+        },
+        error: () => {
+          this.notification.showError('Update Failed.');
+        }
+      })
+  }
+
+  getDeviceConfiguration(deviceName: string): void {
+    this.deviceService.getDeviceConfiguration(deviceName)
+      .pipe(take(1)).subscribe({
+      next: (result: any) => {
+        if (result && result.length > 1) {
+          this.deviceConfig = result[1];
+        }
+      },
+      error: error => {
+        this.notification.showError(`Error: ${error?.message}`);
+      }
+    })
+  }
+
+  downloadConfigFile(): void {
+    const filename = `${this.deviceData.name.toLocaleLowerCase()}_config.txt`;
+    const blob = new Blob([this.deviceConfig], {type: 'text/plain;charset=utf-8'});
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = filename;
+    a.click();
+    URL.revokeObjectURL(url);
+  }
+
+  updateDeviceSettings(): void {
+    let payload: any = {
+      restapi_port: this.deviceSettingsForm.value.portNumber,
+      restapi_account: {
+        restapi_username: this.deviceSettingsForm.value.apiUsername,
+        restapi_password: this.deviceSettingsForm.value.apiPassword,
+      },
+      firewall_ip: {},
+      intranet_ip: {},
+      console_account: {
+        console_username: this.deviceSettingsForm.value.webuiUsername,
+        console_password: this.deviceSettingsForm.value.webuiPassword,
+      },
+      gateway_domain: this.deviceSettingsForm.value.gatewayDomain,
+      location: this.deviceSettingsForm.value.location,
+      device_group: this.deviceSettingsForm.value.groupName,
+      webui_port: this.deviceSettingsForm.value.webuiPortNumber,
+      log_enable: this.deviceSettingsForm.value.enableLog
+    }
+
+    if (this.deviceSettingsForm.invalid) {
+      console.log(this.deviceSettingsForm.value);
+      this.deviceSettingsForm.markAllAsTouched();
+      return;
+    }
+
+    const postDataJsonString: string = JSON.stringify(payload);
+    const encodedPostData: string = encodeURIComponent(postDataJsonString);
+    this.deviceService.updateDevice(this.deviceData?.id, this.deviceData?.type, this.deviceData?.name, encodedPostData)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (result[0]) {
+              this.notification.showSuccess(`${this.deviceData?.name} updated successfully!`);
+              this.backToManagedDevices();
+            } else {
+              this.notification.showError(`Failed to update device: ${result[1]}`);
+            }
+          }
+        },
+        error: () => {
+          this.notification.showError('Update Failed.');
+        }
+      })
+  }
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/device-license-overview.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/device-license-overview.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/device-license-overview.html	(working copy)
@@ -0,0 +1,24 @@
+<h2 mat-dialog-title class="dialog-title">Device License Status - {{ deviceMeta?.name }}</h2>
+
+<mat-dialog-content>
+  <div class="dialog-container">
+    <button mat-raised-button color="basic" (click)="updateLicense()" class="end-item">Update License</button>
+  </div>
+  <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 align="center">
+  <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/components/sub-components/devices/device-update-license.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/device-update-license.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/device-update-license.html	(working copy)
@@ -0,0 +1,31 @@
+<h2 mat-dialog-title class="dialog-title">Update Device License - {{ data.name }}</h2>
+
+<mat-dialog-content>
+  <form
+    [formGroup]="deviceLicenseForm"
+    class="login-form"
+  >
+    <div class="form-field-wrapper">
+      <label for="licenseKey" class="form-label">License Key</label>
+      <mat-form-field appearance="outline" subscriptSizing="dynamic">
+        <textarea matInput rows="5" formControlName="licenseKey" id="licenseKey"
+                  placeholder="Device License Key"></textarea>
+      </mat-form-field>
+    </div>
+  </form>
+</mat-dialog-content>
+<mat-dialog-actions align="center">
+  <button
+    type="submit"
+    mat-button
+    color="basic"
+    (click)="onCancel()">
+    Cancel
+  </button>
+  <button
+    mat-raised-button
+    color="basic"
+    (click)="onSubmit()">
+    Submit
+  </button>
+</mat-dialog-actions>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.html	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.html	(working copy)
@@ -20,7 +20,9 @@
     </ng-container>
     <ng-container matColumnDef="deviceName">
       <th mat-header-cell *matHeaderCellDef> Device Name</th>
-      <td mat-cell *matCellDef="let element"> {{ element.name }}</td>
+      <td mat-cell *matCellDef="let element">
+        <a class="details-page-link" (click)="goToDetails(element)">{{ element.name }}</a>
+      </td>
     </ng-container>
     <ng-container matColumnDef="groupName">
       <th mat-header-cell *matHeaderCellDef> Group Name</th>
@@ -58,7 +60,16 @@
       <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']" class="delete-icon"
+          <fa-icon [icon]="['fas', 'id-badge']" size="lg" (click)="showLicenseDialog(element)"
+                   matTooltip="License"></fa-icon>
+          <a target="_blank"
+             rel="noopener noreferrer"
+             [href]="element?.ip && element?.webui_port ? 'https://' + element.ip + ':' + element.webui_port : '#'">
+            <fa-icon [icon]="['fas', 'tv']" size="lg" matTooltip="WebUI"></fa-icon>
+          </a>
+          <fa-icon [icon]="['fas', 'terminal']" size="lg" (click)="openWebConsole(element)" matTooltip="CLI"></fa-icon>
+          <fa-icon [icon]="['far', 'save']" size="lg" (click)="confirmSave(element)" matTooltip="Save Config"></fa-icon>
+          <fa-icon [icon]="['far', 'trash-can']" size="lg" class="delete-icon" matTooltip="Delete Device"
                    (click)="confirmDelete(element)"></fa-icon>
         </div>
       </td>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.scss	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.scss	(working copy)
@@ -43,7 +43,7 @@
   justify-content: center;
   align-items: center;
   padding-right: 12px;
-  gap: 8px;
+  gap: 12px;
 }
 
 .a-link {
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 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/devices/devices.ts	(working copy)
@@ -9,6 +9,8 @@
 import {Storage} from '../../../services/storage';
 import {FormBuilder, FormGroup, Validators} from '@angular/forms';
 import {CustomValidators} from '../../../utils/custom-validators';
+import {Router} from '@angular/router';
+import {KeyValuePair} from '../device-details/device-details';
 
 @Component({
   selector: 'app-devices', imports: [SharedModule], templateUrl: './devices.html', styleUrl: './devices.scss'
@@ -27,7 +29,8 @@
     private notification: NotificationService,
     private deviceService: DeviceService,
     private confirmationService: Confirmation,
-    private storage: Storage
+    private storage: Storage,
+    private router: Router
   ) {
     this.deviceType = this.storage.getItem('deviceType');
     this.getDeviceGroups();
@@ -137,6 +140,65 @@
           })
       } else {
         this.notification.showSuccess('Deletion cancelled.');
+      }
+    });
+  }
+  openWebConsole(device: any): void {
+    console.log(device);
+    const url = this.router.serializeUrl(
+      this.router.createUrlTree(['/inventory/web-console'], {
+        queryParams: { device_id: device?.id, device_name: device?.name }
+      })
+    );
+    // const url = `/web-console-standalone.html?device_id=${encodeURIComponent(device?.id)}&device_name=${encodeURIComponent(device?.name)}`;
+    window.open(url, '_blank');
+  }
+
+  confirmSave(device: any): void {
+    let deviceName = device?.name;
+    let confirmMsg = `This action will save the device's (${deviceName}) configuration by CLI command 'write memory'. Are you sure you want to do this?`
+    this.confirmationService.openConfirmDialog({
+      title: `Save configuration - ${deviceName}?`,
+      message: confirmMsg,
+      confirmButtonText: 'Yes, Save It',
+      cancelButtonText: 'Cancel',
+      confirmButtonColor: 'warn',
+      cancelButtonColor: 'primary'
+    }).subscribe((result: boolean) => {
+      if (result) {
+        let rawPayload = new FormData();
+        rawPayload.set('action', 'SaveConfigurations')
+        rawPayload.set('options', JSON.stringify({"__pk_list": [JSON.stringify({
+            "name": deviceName,
+            "id": device?.id,
+            "type": device?.type,
+          })]}))
+        this.deviceService.saveDevice(rawPayload)
+          .pipe(take(1))
+          .subscribe({
+            next: (result: any) => {
+              // ToDo: Backend fix required.
+              if (result && result.length > 1) {
+                if (!result[0]) {
+                  this.notification.showError(`${result[1]}`);
+                } else {
+                  this.notification.showSuccess(`${deviceName} saved successfully!`);
+                  this.getDeviceGroups();
+                }
+              }
+            },
+            error: (err) => {
+              if (err.status === 200) {
+                // ToDo: Backend fix required.
+                this.notification.showSuccess(`${deviceName} saved successfully!`);
+                this.getDeviceGroups();
+              } else {
+                this.notification.showError('Save Failed.');
+              }
+            }
+          })
+      } else {
+        this.notification.showSuccess('Save cancelled.');
       }
     });
   }
@@ -158,6 +220,45 @@
       }
     })
   }
+
+  goToDetails(device: any) {
+    this.router.navigate(['/inventory/devices', device.name], {
+      state: { deviceData: device, groups: this.groups }
+    });
+  }
+
+  showLicenseDialog(device: any): void {
+    this.dialogConfig.position = {
+      bottom: '0px', right: '0px',
+    }
+    this.dialogConfig.width = '40%';
+    this.dialogConfig.height = '60%';
+    this.dialogConfig.data = {
+      name: device?.name,
+      model: device?.model,
+      serial_number: device?.serial_number,
+      license_key: device?.license_key,
+      license_date: device?.license_date,
+      license_limit: device?.license_limit,
+      license_feature: device?.license_feature,
+    }
+    const dialogRef = this.dialog.open(DeviceLicenseDialog, this.dialogConfig);
+    dialogRef.afterClosed().subscribe((result: boolean) => {
+      if (result) {
+        this.dialogConfig.data = {
+          id: device?.id,
+          type: device?.type,
+          name: device?.name,
+        }
+        const dialogRef = this.dialog.open(DeviceUpdateLicenseDialog, this.dialogConfig);
+        dialogRef.afterClosed().subscribe(isAdded => {
+          if (isAdded) {
+            this.getDeviceGroups();
+          }
+        })
+      }
+    })
+  }
 }
 
 @Component({
@@ -307,6 +408,8 @@
         console_username: this.deviceForm.value.webuiUsername,
         console_password: this.deviceForm.value.webuiPassword,
       },
+      gateway_domain: this.deviceForm.value.gatewayDomain,
+      location: this.deviceForm.value.location,
       group_name_from: this.deviceForm.value.deviceGroupSource,
       device_group: [{
         name: this.deviceForm.value.groupName
@@ -346,10 +449,137 @@
           this.notification.showError('Creation Failed.');
         }
       })
+
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+}
+
+@Component({
+  selector: 'app-device-license-overview',
+  imports: [SharedModule],
+  templateUrl: './device-license-overview.html'
+})
+export class DeviceLicenseDialog {
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<DeviceLicenseDialog>);
+  licenseDataSource = new MatTableDataSource<KeyValuePair>();
+  licenseColumns: Array<string> = ['key', 'value'];
 
+  deviceMeta: any = {}
+
+
+  constructor() {
+    this.deviceMeta.name = this.data.name;
+    delete this.data.name;
+    this.licenseDataSource = this.processConfigData(this.data);
   }
 
+
+  processConfigData(configData: any): any {
+    let dataSource = new MatTableDataSource<KeyValuePair>();
+    if (configData) {
+      const dataArray: KeyValuePair[] = Object.keys(configData).map(key => ({
+        key: this.formatKey(key), // Format the key for display
+        value: configData[key] === "" ? "N/A" : configData[key] // Display "N/A" for empty strings
+      }));
+      dataSource.data = dataArray;
+    } else {
+      dataSource.data = [];
+    }
+    return dataSource;
+  }
+
+  formatKey(key: string): string {
+    const rawReplacements: { [abbreviation: string]: string } = {
+      'bld': 'build',
+      'hw': 'hardware',
+    };
+
+    let processedKey = key;
+    for (const abbr in rawReplacements) {
+      if (Object.prototype.hasOwnProperty.call(rawReplacements, abbr)) {
+        const pattern = new RegExp(`(^|_)${abbr}(_|$)`, 'gi');
+        processedKey = processedKey.replace(pattern, (match, p1, p2) => {
+          return p1 + rawReplacements[abbr] + p2;
+        });
+        const directPattern = new RegExp(`^${abbr}$`, 'gi');
+        processedKey = processedKey.replace(directPattern, rawReplacements[abbr]);
+      }
+    }
+
+    let formattedKey = processedKey
+      .replace(/_/g, ' ')
+      .replace(/([A-Z])/g, ' $1')
+      .replace(/^./, (str) => str.toUpperCase())
+      .replace(/\s+/g, ' ').trim();
+
+    const wordsToForceUppercase = ['cpu', 'ram', 'ssl'];
+    const uppercasePattern = new RegExp(`\\b(${wordsToForceUppercase.join('|')})\\b`, 'gi');
+    formattedKey = formattedKey.replace(uppercasePattern, (match) => match.toUpperCase());
+
+    return formattedKey;
+  }
+
+  updateLicense() {
+    this.dialogRef.close(true);
+  }
+
   onCancel(): void {
     this.dialogRef.close(false);
   }
+
 }
+
+@Component({
+  selector: 'app-device-update-license',
+  imports: [SharedModule],
+  templateUrl: './device-update-license.html'
+})
+export class DeviceUpdateLicenseDialog {
+  readonly data = inject(MAT_DIALOG_DATA);
+  readonly dialogRef = inject(MatDialogRef<DeviceUpdateLicenseDialog>);
+  deviceLicenseForm: FormGroup;
+
+  constructor(private formBuilder: FormBuilder, private deviceService: DeviceService, private notification: NotificationService) {
+    this.deviceLicenseForm = this.formBuilder.group({
+      licenseKey: ['', Validators.required],
+    })
+  }
+
+  onSubmit() {
+    let payload = {
+      id: this.data?.id,
+      name: this.data?.name,
+      type: this.data?.type,
+    }
+    let rawPayload = new FormData();
+    rawPayload.set('action', 'Activate');
+    rawPayload.set('options', JSON.stringify({"__pk_list": [JSON.stringify(payload)], license_name: this.deviceLicenseForm.value.licenseKey}));
+
+    this.deviceService.updateDeviceLicense(rawPayload)
+      .pipe(take(1))
+      .subscribe({
+        next: (result: any) => {
+          if (result && result.length > 1) {
+            if (result[0]) {
+              this.notification.showSuccess(`${this.data?.name} updated successfully!`);
+              this.dialogRef.close(true);
+            } else {
+              this.notification.showError(`Failed to update device: ${result[1]}`);
+            }
+          }
+        },
+        error: () => {
+          this.notification.showError('Update Failed.');
+        }
+      })
+  }
+
+  onCancel(): void {
+    this.dialogRef.close(false);
+  }
+
+}
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.html
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.html	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.html	(working copy)
@@ -0,0 +1,19 @@
+<div class="title" [class.oem-webconsole]="isOemVersion">{{ title }}</div>
+<div class="content" [class.hide]="isConnected">
+  <div class="info">{{ info }}</div>
+  @if (isLoading) {
+    <div class="spinner">
+      <div class="rect1"></div>
+      <div class="rect2"></div>
+      <div class="rect3"></div>
+      <div class="rect4"></div>
+      <div class="rect5"></div>
+    </div>
+  }
+  @if (!isLoading) {
+    <button class="btn-array" (click)="reconnect()">Reconnect</button>
+  }
+</div>
+<div class="term-container" [class.hide]="!isConnected">
+  <div id="terminal" #terminal></div>
+</div>
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.scss	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.scss	(working copy)
@@ -0,0 +1,68 @@
+$primary-color: #1170cf;
+
+.title {
+  height: 36px;
+  width: 100%;
+  margin-top: 0 !important;
+  padding: 0;
+  color: #fff;
+  font-size: 16px;
+  line-height: 36px;
+  vertical-align: top;
+  background-color: $primary-color;
+}
+
+.hide {
+  display: none;
+  opacity: 0;
+}
+
+.content {
+  margin: 0 auto;
+  padding-top: 100px;
+}
+
+.content .info {
+  color: #333;
+  font-size: 14px;
+  text-align: center;
+  margin-bottom: 30px;
+}
+
+.content .btn-array {
+  display: block;
+  margin: 0 auto;
+  padding: 8px 20px;
+  font-size: 14px;
+  color: #e4701d;
+  background-color: #fff;
+  border: 1px solid #e4701d;
+  border-radius: 5px;
+}
+
+.content .btn-array:active:focus, .content .btn-array:focus {
+  outline: 0;
+}
+
+
+.spinner {
+  margin: 50px auto;
+  width: 50px;
+  height: 30px;
+  text-align: center;
+  font-size: 10px;
+}
+
+.spinner > div {
+  background-color: #e4701e;
+  height: 100%;
+  width: 6px;
+  display: inline-block;
+  -webkit-animation: stretchdelay 1.2s infinite ease-in-out;
+  animation: stretchdelay 1.2s infinite ease-in-out;
+}
+
+.spinner .rect2 { -webkit-animation-delay: -1.1s; animation-delay: -1.1s; }
+.spinner .rect3 { -webkit-animation-delay: -1.0s; animation-delay: -1.0s; }
+.spinner .rect4 { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; }
+.spinner .rect5 { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.spec.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.spec.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.spec.ts	(working copy)
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { WebConsole } from './web-console';
+
+describe('WebConsole', () => {
+  let component: WebConsole;
+  let fixture: ComponentFixture<WebConsole>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [WebConsole]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(WebConsole);
+    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/web-console/web-console.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.ts	(nonexistent)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/components/sub-components/web-console/web-console.ts	(working copy)
@@ -0,0 +1,375 @@
+import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, ViewChild, Inject, PLATFORM_ID } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { HttpClient } from '@angular/common/http';
+import { isPlatformBrowser } from '@angular/common';
+import 'xterm/css/xterm.css';
+import {URLS} from '../../../constants/api_urls';
+
+@Component({
+  selector: 'app-web-console',
+  standalone: true,
+  templateUrl: './web-console.html',
+  styleUrls: ['./web-console.scss']
+})
+export class WebConsole implements OnInit, AfterViewInit, OnDestroy {
+
+  @ViewChild('terminal', { static: false }) terminalElementRef!: ElementRef;
+  private terminal: import('xterm').Terminal | null = null;
+  private fitAddon: import('xterm-addon-fit').FitAddon | null = null;
+  private resizeObserver: ResizeObserver | null = null;
+  title = 'Web Console';
+  info = 'Connecting...';
+  isConnected = false;
+  isLoading = true;
+  isOemVersion = false;
+  private deviceId: string = '';
+  private deviceName: string = '';
+  private pid: string = '';
+  private sid: number = 0;
+  private retryCount = 0;
+  private qTimer: any;
+  private kbQueue: string[] = [];
+  private qTime = 100;
+
+  constructor(
+    private route: ActivatedRoute,
+    private httpClient: HttpClient,
+    @Inject(PLATFORM_ID) private platformId: Object
+  ) {}
+
+  ngOnInit(): void {
+    this.route.queryParams.subscribe(params => {
+      this.deviceId = params['device_id'] || 'localhost';
+      this.deviceName = params['device_name'] || '';
+      this.setTitle();
+      this.checkOemVersion();
+    });
+  }
+
+  async ngAfterViewInit(): Promise<void> {
+    if (isPlatformBrowser(this.platformId)) {
+      const { Terminal } = await import('xterm');
+      const { FitAddon } = await import('xterm-addon-fit');
+
+      if (this.terminalElementRef?.nativeElement) {
+        this.terminal = new Terminal({
+          convertEol: true,
+          cursorBlink: true,
+          fontFamily: 'monospace',
+          fontSize: 14,
+          cols: 80,
+          rows: 60,
+          allowProposedApi: true,
+          theme: {
+            background: '#000000',
+            foreground: '#FFFFFF',
+            cursor: '#FFFFFF',
+            selectionBackground: '#5f6062',
+            black: '#000000',
+            red: '#e06c75',
+            green: '#98c379',
+            yellow: '#e5c07b',
+            blue: '#61afef',
+            magenta: '#c678dd',
+            cyan: '#56b6c2',
+            white: '#abb2bf',
+            brightBlack: '#5c6370',
+            brightRed: '#e06c75',
+            brightGreen: '#98c379',
+            brightYellow: '#e5c07b',
+            brightBlue: '#61afef',
+            brightMagenta: '#c678dd',
+            brightCyan: '#56b6c2',
+            brightWhite: '#ffffff'
+          }
+        });
+
+        this.fitAddon = new FitAddon();
+        this.terminal.loadAddon(this.fitAddon);
+
+        this.terminal.open(this.terminalElementRef.nativeElement);
+
+        this.fitAddon.fit();
+
+        // Initialize ResizeObserver
+        this.resizeObserver = new ResizeObserver(() => {
+          if (this.terminal && this.fitAddon) {
+            this.fitAddon.fit();
+          }
+        });
+        this.resizeObserver.observe(this.terminalElementRef.nativeElement);
+
+        this.terminal.write('\x1b[32m[WebConsole Initialized]\x1b[0m\r\n'); // Welcome message for debugging
+
+        setTimeout(() => this.connect(), 500);
+      } else {
+        console.error('Terminal element not found');
+        this.info = 'Terminal initialization failed.';
+        this.isLoading = false;
+      }
+    } else {
+      this.info = 'Terminal is not available in this environment.';
+      this.isLoading = false;
+    }
+  }
+
+  ngOnDestroy(): void {
+    if (this.qTimer) {
+      clearTimeout(this.qTimer);
+    }
+    if (this.resizeObserver) {
+      this.resizeObserver.disconnect();
+      this.resizeObserver = null;
+    }
+    if (this.terminal && isPlatformBrowser(this.platformId)) {
+      this.terminal.dispose();
+      this.terminal = null;
+      this.fitAddon = null;
+    }
+  }
+
+  // Adjusted to better handle the HTML structure and preserve newlines/spaces
+  private convertHtmlToAnsi(xmlContentString: string): string {
+    // The server response starts with <?xml...?> then <c cy="012"/> then <span>...</span>
+    // The DOMParser will fail if given raw text without a single root, or if XML declaration is present.
+    // We need to extract the relevant content that we want to parse as HTML/XML fragments.
+    // From your example, the text and spans appear after the <c/> tag.
+    // Let's assume the main content is within <span> tags or as direct text nodes after <c/>
+    // For reliable parsing, we'll strip the XML declaration and wrap everything in a custom root.
+    const cleanResponse = xmlContentString.replace(/<\?xml.*?\?>/, '').trim();
+    const wrappedXml = `<root>${cleanResponse}</root>`;
+    const parser = new DOMParser();
+    const xmlDoc = parser.parseFromString(wrappedXml, 'text/xml');
+
+    let ansiOutput = '';
+
+    // Define ANSI mapping - YOU NEED TO COMPLETE THESE BASED ON YOUR BACKEND'S 'ff be', 'ff bc' MEANINGS
+    const ansiColorMap: { [key: string]: { start: string, end: string } } = {
+      'ff be': { start: '\x1b[38;5;15;48;5;236m', end: '\x1b[0m' }, // Example: light gray on dark gray for normal text
+      'ff bc': { start: '\x1b[38;5;15;48;5;236m', end: '\x1b[0m' }, // Assuming these are similar default styles
+      // Add more mappings as needed:
+      // 'error-class': { start: '\x1b[31m', end: '\x1b[0m' }, // Red text
+      // 'bold-text': { start: '\x1b[1m', end: '\x1b[0m' }, // Bold text
+      // Note: The example response has "ff be" and "ff bc" spanning entire lines,
+      // which implies they might be the primary styling for standard text.
+      // You might need to experiment to get the exact color mappings.
+    };
+
+
+    // The server response appears to send whole lines (or segments of lines) in spans
+    // It's crucial to preserve whitespace and newlines as the server sends them.
+    // DOMParser might normalize whitespace, so let's try to grab innerHTML/textContent directly.
+
+    // Iterate over the children of the <root> element (which are <c/> and <span> elements or text)
+    Array.from(xmlDoc.documentElement.childNodes).forEach(node => {
+      if (node.nodeType === Node.ELEMENT_NODE) {
+        const element = node as HTMLElement;
+        if (element.tagName.toLowerCase() === 'span') {
+          const cls = element.getAttribute('class') || '';
+          const mapping = ansiColorMap[cls] || { start: '', end: '' }; // Fallback to no styling
+          ansiOutput += mapping.start;
+          ansiOutput += element.textContent || ''; // Get plain text content of the span
+          ansiOutput += mapping.end;
+        }
+        // You might have other custom tags like <br/> that need to be translated to \r\n
+        // or <tab/> to \t, etc.
+      } else if (node.nodeType === Node.TEXT_NODE && node.textContent !== null) {
+        // Capture any raw text nodes not wrapped in spans
+        ansiOutput += node.textContent;
+      }
+    });
+
+
+    // Replace HTML entities after content extraction
+    ansiOutput = ansiOutput.replace(/&nbsp;/g, ' ');
+    ansiOutput = ansiOutput.replace(/&amp;/g, '&');
+    ansiOutput = ansiOutput.replace(/&lt;/g, '<');
+    ansiOutput = ansiOutput.replace(/&gt;/g, '>');
+
+    // Xterm.js `convertEol: true` handles '\n'.
+    // The screenshot shows newlines as actual newlines in the XML, so they should be preserved.
+    // However, sometimes browsers/DOMParser might normalize multiple spaces or trim leading/trailing spaces from text nodes.
+    // If the output still looks squished, you might need to enforce a specific column width
+    // and padding, or ensure the backend sends precise whitespace (e.g., using a fixed-width font implies fixed character cells).
+
+    return ansiOutput;
+  }
+
+
+  private setTitle(): void {
+    this.title = this.deviceId === 'localhost' ? 'AMP Web Console' : `Web Console (${this.deviceName})`;
+  }
+
+  private checkOemVersion(): void {
+    const version = localStorage.getItem('amp_version');
+    try {
+      if (typeof version === 'string') {
+        this.isOemVersion = !JSON.parse(version)?.version.includes('AMP');
+      }
+    } catch {
+      this.isOemVersion = false;
+    }
+  }
+
+  private connect(): void {
+    if (!isPlatformBrowser(this.platformId)) {
+      return;
+    }
+    this.httpClient.get(`${URLS.WEBCONSOLE_INIT_URL}?device_id=${this.deviceId}`, { responseType: 'json' })
+      .subscribe({
+        next: (data: any) => {
+          if (!data[0]) {
+            this.disconnect();
+            this.info = data[1];
+          } else {
+            this.pid = data[1];
+            this.sid = data[2];
+            this.isConnected = true;
+            this.isLoading = false;
+            if (this.terminal) {
+              this.registerKeyEvents();
+            }
+            this.update(); // Start polling for updates
+          }
+        },
+        error: () => {
+          this.disconnect();
+          this.info = 'Failed to connect console.';
+        }
+      });
+  }
+
+  private update(): void {
+    if (!isPlatformBrowser(this.platformId)) return;
+
+    // Send the keyboard queue in the order it was received
+    const send = this.kbQueue.join('');
+    this.kbQueue = []; // Clear the queue after preparing to send
+
+    this.httpClient.get(
+      `${URLS.WEBCONSOLE_HANDLE_REQ_URL}?s=${this.sid}&pid=${this.pid}&w=160&h=24&k=${encodeURIComponent(send)}`,
+      { responseType: 'text' } // Receive as text to parse XML
+    ).subscribe({
+      next: (response: string) => {
+        // Remove XML declaration and wrap in a root element for reliable parsing
+        const cleanResponse = response.replace(/<\?xml.*?\?>/, '').trim();
+        const wrappedXml = `<root>${cleanResponse}</root>`;
+        const doc = new DOMParser().parseFromString(wrappedXml, 'text/xml');
+
+        const errorNode = doc.querySelector('Error');
+        if (errorNode) {
+          this.disconnect();
+          this.info = errorNode.textContent || response;
+          return;
+        }
+
+        const cNode = doc.querySelector('c');
+        const cy = cNode?.getAttribute('cy');
+        let cursorRow = 1; // Default to first row
+        if (cy) {
+          cursorRow = Math.max(parseInt(cy, 10), 1); // Ensure at least 1
+        }
+
+        const ansiOutput = this.convertHtmlToAnsi(cleanResponse); // Pass the raw clean string to the converter
+
+        if (this.terminal) {
+          // Clear the line and move cursor to the specified row and column 1
+          // \x1b[2K clears the current line
+          // \x1b[<row>;1H moves the cursor to row, col 1
+          this.terminal.write(`\x1b[${cursorRow};1H\x1b[2K`);
+          this.terminal.write(ansiOutput); // Write the converted content
+        }
+
+
+        // Manage polling frequency
+        if (ansiOutput.trim().length > 0) { // If there was any meaningful output
+          this.qTime = 100; // Reset to fast poll if activity
+        } else {
+          this.qTime = Math.min(this.qTime * 2, 2000); // Slow down if no output
+        }
+        this.retryCount = 0; // Reset retry count on successful response
+
+        // Schedule next update
+        this.qTimer = setTimeout(() => this.update(), this.qTime);
+      },
+      error: (err) => {
+        console.error('Update request error:', err);
+        this.retryCount++;
+        if (this.retryCount < 3) {
+          this.qTimer = setTimeout(() => this.update(), 1000);
+        } else {
+          this.disconnect();
+          this.info = 'Network error: ' + (err.message || 'Unknown');
+        }
+      }
+    });
+  }
+
+  private registerKeyEvents(): void {
+    if (this.terminal) {
+      this.terminal.onKey(({ key, domEvent }: { key: string; domEvent: KeyboardEvent }) => {
+        const code = domEvent.keyCode || domEvent.which;
+        let k = key;
+        // Handle Ctrl + A-Z
+        if (domEvent.ctrlKey && code >= 65 && code <= 90) {
+          k = String.fromCharCode(code - 64);
+        } else if (code === 8) { // Backspace
+          k = String.fromCharCode(127); // ASCII DEL character for backspace
+        } else if ([9, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46].includes(code)) {
+          k = this.mapSpecialKey(code);
+        }
+
+        // Add the key to the queue
+        this.kbQueue.push(encodeURIComponent(k));
+        this.qTime = 100; // Reset polling speed when there's input
+        if (!this.qTimer) { // If timer isn't running, start an immediate update
+          this.update();
+        }
+        domEvent.preventDefault(); // Prevent default browser behavior for terminal keys
+      });
+    }
+  }
+
+  private mapSpecialKey(code: number): string {
+    const keyMap: { [key: number]: string } = {
+      9: '\t',      // Tab
+      27: '\x1b',    // Esc
+      33: '\x1b[5~', // Page Up (CSI 5 ~)
+      34: '\x1b[6~', // Page Down (CSI 6 ~)
+      35: '\x1b[F',  // End (CSI F) - often equivalent to \x1b[4~
+      36: '\x1b[H',  // Home (CSI H) - often equivalent to \x1b[1~
+      37: '\x1b[D',  // Left Arrow (CSI D)
+      38: '\x1b[A',  // Up Arrow (CSI A)
+      39: '\x1b[C',  // Right Arrow (CSI C)
+      40: '\x1b[B',  // Down Arrow (CSI B)
+      45: '\x1b[2~', // Insert (CSI 2 ~)
+      46: '\x1b[3~'  // Delete (CSI 3 ~)
+    };
+    return keyMap[code] || String.fromCharCode(code);
+  }
+
+  reconnect(): void {
+    this.isLoading = true;
+    this.info = 'Connecting...';
+    if (this.terminal && isPlatformBrowser(this.platformId)) {
+      this.terminal.dispose();
+      this.terminal = null;
+      this.fitAddon = null;
+      if (this.resizeObserver) {
+        this.resizeObserver.disconnect();
+        this.resizeObserver = null;
+      }
+    }
+    this.connect();
+  }
+
+  private disconnect(): void {
+    this.isConnected = false;
+    this.isLoading = false;
+    this.info = 'Console disconnected.';
+    if (this.qTimer) {
+      clearTimeout(this.qTimer);
+      this.qTimer = null;
+    }
+  }
+}
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 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/constants/api_urls.ts	(working copy)
@@ -1,5 +1,6 @@
 const PREFIX = '/api';
 
+// These are base URLs, Query params might be added at respective services.
 export const URLS = {
   LOGIN_URL: `${PREFIX}/login_app`,
   GET_DEVICE_GROUPS_URL: `${PREFIX}/api/cm/device_mgmt/device_group/RoleDeviceGroup/_perform`,
@@ -8,5 +9,13 @@
   CHECK_DEVICE_BUILD_INFO_URL: `${PREFIX}/cm/check_build_info`,
   GET_DEVICE_TYPE_URL: `${PREFIX}/cm/get_device_type`,
   ADD_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_add`,
-  DELETE_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_delete`
+  DELETE_DEVICE_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_delete`,
+  UPDATE_DEVICE_CREDS_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_update`,
+  GET_DEVICE_CONFIG_URL: `${PREFIX}/cm/configuration/configuration_file/get_config/device`,
+  UPDATE_DEVICE_MONITORING_STATE_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_perform`,
+  SAVE_DEVICE_CONFIG_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_perform`,
+  UPDATE_DEVICE_LICENSE_URL: `${PREFIX}/api/cm/device_mgmt/device/Device/_perform`,
+  WEBCONSOLE_INIT_URL: `${PREFIX}/cm/ajax/webconsole/_init`,
+  WEBCONSOLE_HANDLE_REQ_URL: `${PREFIX}/cm/ajax/webconsole/_handle_request`,
+
 } as const; // Makes properties readonly
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/guards/auth-guard.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/guards/auth-guard.ts	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/guards/auth-guard.ts	(working copy)
@@ -1,8 +1,8 @@
 import { Injectable } from '@angular/core';
 import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
-import { Observable } from 'rxjs';
+import { Observable, combineLatest, of } from 'rxjs'; // Import combineLatest and of
 import { Auth } from '../services/auth';
-import { map, take } from 'rxjs/operators';
+import { map, take, switchMap, filter } from 'rxjs/operators'; // Import filter
 
 @Injectable({
   providedIn: 'root'
@@ -12,43 +12,45 @@
 
   canActivate(
     route: ActivatedRouteSnapshot,
-    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
+    state: RouterStateSnapshot
+  ): Observable<boolean | UrlTree> { // Return type is now consistently Observable<...>
 
-    const user = this.authService.currentUserValue;
+    // Wait until the initial authentication check is complete, then proceed.
+    return this.authService.isAuthenticating.pipe(
+      filter(isAuthInProgress => !isAuthInProgress), // Wait until isAuthenticating emits false
+      take(1), // Take the first false value and complete
+      switchMap(() => {
+        // Now that initial authentication is done, check currentUserValue
+        const user = this.authService.currentUserValue;
 
-    if (user) {
-      if (route.data['roles']) {
-        const requiredRoles = route.data['roles'] as string[];
-        const hasRequiredRoles = requiredRoles.some((role) => this.authService.hasRole(role));
-        if (!hasRequiredRoles) {
-          return this.router.createUrlTree(['/login']);
-        }
-      }
+        if (user) {
+          // User is authenticated, now check roles and permissions
+          if (route.data['roles']) {
+            const requiredRoles = route.data['roles'] as string[];
+            const hasRequiredRoles = requiredRoles.some((role) => this.authService.hasRole(role));
+            if (!hasRequiredRoles) {
+              console.warn('AuthGuard: User lacks required roles. Redirecting to login.');
+              return of(this.router.createUrlTree(['/login']));
+            }
+          }
 
-      if (route.data['permissions']) {
-        const requiredPermissions = route.data['permissions'] as string[];
-        const hasPermissions = requiredPermissions.some((permission) => this.authService.hasPermission(permission));
-        if (!hasPermissions) {
-          return this.router.createUrlTree(['/login']);
-        }
-      }
+          if (route.data['permissions']) {
+            const requiredPermissions = route.data['permissions'] as string[];
+            const hasPermissions = requiredPermissions.some((permission) => this.authService.hasPermission(permission));
+            if (!hasPermissions) {
+              console.warn('AuthGuard: User lacks required permissions. Redirecting to login.');
+              return of(this.router.createUrlTree(['/login']));
+            }
+          }
 
-      return true; // The User is logged in and has required roles/permissions.
-    }
-
-    return this.router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url} });
-    // return this.authService.isLoggedIn.pipe(
-    //   take(1), // Take the first value and complete
-    //   map((isLoggedIn: boolean) => {
-    //     if (isLoggedIn) {
-    //       return true; // User is logged in, allow access
-    //     } else {
-    //       // User is not logged in, redirect to login page
-    //       // return this.router.createUrlTree(['/login']);
-    //       this.authService.logout();
-    //       return false;
-    //     }
-    //   })
-    // );
+          // User is authenticated and has required roles/permissions
+          return of(true);
+        } else {
+          // User is not authenticated, redirect to login
+          console.log('AuthGuard: User not authenticated. Redirecting to login.');
+          return of(this.router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }));
+        }
+      })
+    );
   }
 }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/interceptors/loading-interceptor-interceptor.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/interceptors/loading-interceptor-interceptor.ts	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/interceptors/loading-interceptor-interceptor.ts	(working copy)
@@ -5,18 +5,26 @@
 
 export const loadingInterceptorFn: HttpInterceptorFn = (req, next) => {
   const loadingService = inject(Loading);
-  let activeRequests = 0;
 
-  if (activeRequests === 0) {
-    loadingService.show();
+  const BYPASS_URL_SEGMENT = '/cm/ajax/webconsole/_handle_request';
+  const shouldBypassLoading = req.url.includes(BYPASS_URL_SEGMENT);
+
+  let activeRequestsForThisCall = 0;
+
+  if (!shouldBypassLoading) {
+    if (activeRequestsForThisCall === 0) {
+      loadingService.show();
+    }
+    activeRequestsForThisCall++;
   }
-  activeRequests++;
 
   return next(req).pipe(
     finalize(() => {
-      activeRequests--;
-      if (activeRequests === 0) {
-        loadingService.hide();
+      if (!shouldBypassLoading) {
+        activeRequestsForThisCall--;
+        if (activeRequestsForThisCall === 0) {
+          loadingService.hide();
+        }
       }
     })
   );
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/auth.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/auth.ts	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/auth.ts	(working copy)
@@ -4,7 +4,7 @@
 import {Storage} from './storage'
 import {ErrorResponse, User} from '../models/user';
 import {HttpService} from './http';
-import {map} from 'rxjs/operators';
+import {map, tap} from 'rxjs/operators'; // Import 'tap'
 import {URLS} from '../constants/api_urls';
 
 @Injectable({
@@ -14,7 +14,13 @@
 
   public currentUser: Observable<User | null>;
   private currentUserSubject: BehaviorSubject<User | null>;
-  // ToDo: fetch from API
+
+  // New: Subject to indicate if initial authentication check is in progress
+  private _isAuthenticating: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
+  public readonly isAuthenticating: Observable<boolean> = this._isAuthenticating.asObservable();
+
+
+  // ToDo: fetch from API (these should ideally come from backend after login/token validation)
   private _roles: string[] = ['super_admin', 'device_admin', 'common_admin'];
   private _permissions: string[] = [
     'Dashboard',
@@ -50,39 +56,74 @@
     private router: Router,
     private storage: Storage,
     protected http: HttpService) {
-    this.currentUserSubject = new BehaviorSubject<User | null>(JSON.parse(storage.getItem('currentUser') || 'null'));
+
+    // Initialize currentUserSubject, but don't assume its validity yet
+    this.currentUserSubject = new BehaviorSubject<User | null>(null);
     this.currentUser = this.currentUserSubject.asObservable();
+
+    // Perform initial authentication check asynchronously
+    this.checkInitialAuthStatus();
   }
 
   public get currentUserValue(): User | null {
     return this.currentUserSubject.value;
   }
 
+  private checkInitialAuthStatus(): void {
+    const storedUser = this.storage.getItem('currentUser');
+    if (storedUser) {
+      const user: User = JSON.parse(storedUser);
+      // Simulate an asynchronous check (e.g., token validation API call)
+      // In a real app, you would make an HTTP call here to validate the user's token.
+      // For now, we'll just simulate a delay and assume validity.
+      setTimeout(() => {
+        // If validation is successful:
+        this.currentUserSubject.next(user);
+        this._isAuthenticating.next(false); // Authentication check complete
+        console.log('Initial auth status: User found and validated.');
+      }, 200); // Simulate 200ms delay for validation
+    } else {
+      this.currentUserSubject.next(null);
+      this._isAuthenticating.next(false); // Authentication check complete
+      console.log('Initial auth status: No user found.');
+    }
+  }
+
+
   login(username: string, password: string): Observable<User | ErrorResponse | any> {
     const formData = new FormData();
     formData.append('username', username);
     formData.append('password', password);
+
+    // Set authenticating to true while login request is in progress
+    this._isAuthenticating.next(true);
+
     return this.http
       .post(URLS.LOGIN_URL, formData, {isFormData: true})
       .pipe(
         map((response: User | ErrorResponse | any) => {
           if ('error_code' in response && response.error_code !== 0) {
+            // Login failed, stop authenticating
+            this._isAuthenticating.next(false);
             throw new Error(response.msg);
           }
           // ToDo: Remove the hardcode with API update.
           if (!('roles' in response)) {
-            response['roles'] = this._roles;
+            response['roles'] = this._roles; // Assign default roles if not from API
           }
           if (!('permissions' in response)) {
-            response['permissions'] = this._permissions;
+            response['permissions'] = this._permissions; // Assign default permissions
           }
 
           this.storage.setItem('currentUser', JSON.stringify(response));
           this.currentUserSubject.next(response);
+          this._isAuthenticating.next(false); // Authentication check complete
           return response;
         }),
         catchError((error: any) => {
           console.error('Login error:', error);
+          this.currentUserSubject.next(null); // Clear user on error
+          this._isAuthenticating.next(false); // Authentication check complete
           return throwError(() => error);
         })
       );
@@ -91,15 +132,18 @@
   logout(): void {
     this.storage.removeItem('currentUser');
     this.currentUserSubject.next(null);
+    // When logging out, we are no longer "authenticating", and there's no user.
+    this._isAuthenticating.next(false);
+    this.router.navigate(['/login']); // Navigate to login after logout
   }
 
   hasRole(role: string): boolean {
-    const user = this.currentUserValue;
+    const user = this.currentUserSubject.value; // Use currentUserSubject.value directly
     return user ? user.roles.includes(role) : false;
   }
 
   hasPermission(permission: string): boolean {
-    const user = this.currentUserValue;
+    const user = this.currentUserSubject.value; // Use currentUserSubject.value directly
     return user ? user.permissions.includes(permission) : false;
   }
 }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/device-service.ts
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/device-service.ts	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/services/device-service.ts	(working copy)
@@ -49,4 +49,41 @@
       csrfInFormData: true
     });
   }
+
+  updateDevice(deviceId: any, deviceType: any, deviceName: any, encodedPostData: any) {
+    const baseUrl = URLS.UPDATE_DEVICE_CREDS_URL
+    const finalUrl = `${baseUrl}/id/%22${deviceId}%22/type/%22${deviceType}%22/name/%22${deviceName}%22?post_data=${encodedPostData}`;
+    return this.http.post(finalUrl, {}, {
+      csrf: true,
+      csrfInFormData: true
+    });
+  }
+
+  getDeviceConfiguration(deviceName: string) {
+    return this.http.get(`${URLS.GET_DEVICE_CONFIG_URL}/${deviceName}`);
+  }
+
+  updateDeviceMonitoringState(rawPayload: any) {
+    return this.http.post(URLS.UPDATE_DEVICE_MONITORING_STATE_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  saveDevice(rawPayload: any) {
+    return this.http.post(URLS.SAVE_DEVICE_CONFIG_URL, rawPayload, {
+      csrf: true,
+      isFormData: true,
+      csrfInFormData: true
+    });
+  }
+
+  updateDeviceLicense(rawPayload: any) {
+    return this.http.post(URLS.UPDATE_DEVICE_LICENSE_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 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/app/shared/shared-module.ts	(working copy)
@@ -21,6 +21,9 @@
   faChevronRight,
   faAngleDoubleLeft,
   faAngleDoubleRight,
+  faTv,
+  faTerminal,
+  faIdBadge,
 } from '@fortawesome/free-solid-svg-icons';
 import {
   faBell,
@@ -32,6 +35,8 @@
   faCircleXmark,
   faTrashCan,
   faCheckCircle,
+  faCircleLeft,
+  faSave,
 } from '@fortawesome/free-regular-svg-icons';
 import {MatGridListModule} from '@angular/material/grid-list';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -127,6 +132,11 @@
       faCircleXmark,
       faTrashCan,
       faCheckCircle,
+      faCircleLeft,
+      faSave,
+      faTv,
+      faTerminal,
+      faIdBadge,
     );
   }
 }
Index: /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/styles.scss
===================================================================
--- /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/styles.scss	(revision 2658)
+++ /branches/amp_4_0/src/webui/webui/htdocs/new/src/gui/src/styles.scss	(working copy)
@@ -42,6 +42,8 @@
   )
 ));
 
+$primary-color: #1170cf;
+
 html, body {
   @include mat.core-theme($theme);
   @include mat.all-component-themes($theme);
@@ -136,7 +138,7 @@
   justify-content: center;
   align-items: center;
   padding-right: 12px;
-  gap: 8px;
+  gap: 12px;
 }
 
 .a-link {
@@ -169,3 +171,22 @@
   flex-grow: 1; /* Allow the form field to take up available space */
   max-width: 300px; /* Example: Limit the maximum width of the input */
 }
+
+.details-page-link,
+.back-to-main-page {
+  cursor: pointer;
+  color: $primary-color;
+}
+
+.dialog-title {
+  color: $primary-color !important;
+}
+
+.dialog-container {
+  display: flex;
+  padding: 10px;
+}
+
+.end-item {
+  margin-left: auto;
+}
