KNOX-3197: Upgrade homepage UI to node 22, angular 20+ ang bootstrap … (#1104)

* KNOX-3197: Upgrade homepage UI to node 22, angular 20+ ang bootstrap to 5+

* KNOX-3197: Updated corejs, swal2, rxjs, fixed the homepage service

* KNOX-3197: Fixed openApiServiceModal bug, changed font size, minor css changes, fixed paginator opacity
diff --git a/knox-homepage-ui/.eslintrc.json b/knox-homepage-ui/.eslintrc.json
deleted file mode 100644
index 165b72f..0000000
--- a/knox-homepage-ui/.eslintrc.json
+++ /dev/null
@@ -1,98 +0,0 @@
-{
-  "root": true,
-  "overrides": [
-    {
-      "files": [
-        "*.ts"
-      ],
-      "parserOptions": {
-        "project": [
-          "home/tsconfig.json"
-        ],
-        "createDefaultProgram": true
-      },
-      "extends": [
-        "plugin:@angular-eslint/recommended",
-        "plugin:@angular-eslint/template/process-inline-templates"
-      ],
-      "rules": {
-        "@typescript-eslint/naming-convention": [
-          "error",
-          {
-            "selector": ["class"],
-            "format": ["PascalCase"]
-          }
-        ],
-        "spaced-comment": "error",
-        "curly": "error",
-        "eol-last": "error",
-        "guard-for-in": "error",
-        "no-unused-labels": "error",
-        "max-len": [
-          "error",
-          { "code": 140 }
-        ],
-        "@typescript-eslint/explicit-member-accessibility": [
-          "off",
-          {"accessibility": "no-public"}
-        ],
-        "@typescript-eslint/member-ordering": [
-          "error",
-          { "default": ["static-field", "instance-field", "field", "method"] }
-        ],
-        "no-caller": "error",
-        "no-bitwise": "error",
-        "no-console": "off",
-        "no-new-wrappers": "error",
-        "no-debugger": "error",
-        "no-redeclare": "off",
-        "no-empty": "error",
-        "no-eval": "error",
-        "@typescript-eslint/no-inferrable-types": "error",
-        "no-shadow": "error",
-        "dot-notation": "off",
-        "no-fallthrough": "error",
-        "no-trailing-spaces": "error",
-        "no-unused-expressions": "error",
-        "no-var": "error",
-        "sort-keys": "off",
-        "brace-style": ["error","1tbs", { "allowSingleLine": true }],
-        "quotes": ["error", "single"],
-        "radix": "error",
-        "@typescript-eslint/semi": "error",
-        "eqeqeq": ["error", "always", {"null": "ignore"}],
-        "@typescript-eslint/type-annotation-spacing": "error",
-        "@angular-eslint/directive-selector": [
-          "error",
-          {
-            "type": "attribute",
-            "prefix": "app",
-            "style": "camelCase"
-          }
-        ],
-        "@angular-eslint/component-selector": [
-          "error",
-          {
-            "type": "element",
-            "prefix": "app",
-            "style": "kebab-case"
-          }
-        ],
-        "@angular-eslint/no-input-rename": "off",
-        "@angular-eslint/no-output-rename": "error",
-        "@angular-eslint/use-pipe-transform-interface": "error",
-        "@angular-eslint/component-class-suffix": "error",
-        "@angular-eslint/directive-class-suffix": "error"
-      }
-    },
-    {
-      "files": [
-        "*.html"
-      ],
-      "extends": [
-        "plugin:@angular-eslint/template/recommended"
-      ],
-      "rules": {}
-    }
-  ]
-}
diff --git a/knox-homepage-ui/angular.json b/knox-homepage-ui/angular.json
index 2e90da6..aadf736 100644
--- a/knox-homepage-ui/angular.json
+++ b/knox-homepage-ui/angular.json
@@ -36,12 +36,16 @@
       "prefix": "app",
       "architect": {
         "build": {
-          "builder": "@angular-devkit/build-angular:browser",
+          "builder": "@angular/build:application",
           "options": {
-            "outputPath": "target/classes/home/app",
+            "outputPath": {
+              "base": "target/classes/home/app",
+              "browser": ""
+            },
             "index": "home/index.html",
-            "main": "home/main.ts",
-            "polyfills": "home/polyfills.ts",
+            "polyfills": [
+              "home/polyfills.ts"
+            ],
             "tsConfig": "home/tsconfig.json",
             "assets": [
               "home/favicon.ico",
@@ -52,13 +56,12 @@
               "home/styles.css"
             ],
             "scripts": [
-              "node_modules/jquery/dist/jquery.min.js",
-              "node_modules/bootstrap/dist/js/bootstrap.js"
-            ]
+              "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
+            ],
+            "browser": "home/main.ts"
           },
           "configurations": {
             "production": {
-              "buildOptimizer": false,
               "aot": false,
               "fileReplacements": [
                 {
@@ -69,10 +72,8 @@
               "outputHashing": "all"
             },
             "development": {
-              "buildOptimizer": false,
               "aot": false,
               "optimization": false,
-              "vendorChunk": true,
               "extractLicenses": false,
               "sourceMap": true,
               "namedChunks": true
@@ -81,21 +82,21 @@
           "defaultConfiguration": "production"
         },
         "serve": {
-          "builder": "@angular-devkit/build-angular:dev-server",
+          "builder": "@angular/build:dev-server",
           "configurations": {
             "production": {
-              "browserTarget": "home:build:production"
+              "buildTarget": "home:build:production"
             },
             "development": {
-              "browserTarget": "home:build:development"
+              "buildTarget": "home:build:development"
             }
           },
           "defaultConfiguration": "development"
         },
         "extract-i18n": {
-          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "builder": "@angular/build:extract-i18n",
           "options": {
-            "browserTarget": "home:build"
+            "buildTarget": "home:build"
           }
         },
         "lint": {
@@ -109,12 +110,5 @@
         }
       }
     }
-  },
-  "defaultProject": "home",
-  "cli": {
-    "schematicCollections": [
-      "@angular-eslint/schematics",
-      "@angular-eslint/schematics"
-    ]
   }
 }
diff --git a/knox-homepage-ui/eslint.config.js b/knox-homepage-ui/eslint.config.js
new file mode 100644
index 0000000..ed79a13
--- /dev/null
+++ b/knox-homepage-ui/eslint.config.js
@@ -0,0 +1,74 @@
+import js from "@eslint/js";
+import tseslint from "typescript-eslint";
+import angular from "@angular-eslint/eslint-plugin";
+import angularTemplate from "@angular-eslint/eslint-plugin-template";
+import tsParser from "@typescript-eslint/parser";
+
+export default [
+    {
+        ignores: ["**/dist/**", "**/node_modules/**"],
+    },
+
+    js.configs.recommended,
+
+    {
+        files: ["**/*.ts"],
+        languageOptions: {
+            parser: tsParser,
+            parserOptions: {
+                project: ["./tsconfig.json"],
+                tsconfigRootDir: import.meta.dirname,
+            },
+            globals: {
+                console: "readonly",
+                window: "readonly",
+                document: "readonly",
+                setTimeout: "readonly",
+            },
+        },
+        plugins: {
+            "@typescript-eslint": tseslint.plugin,
+            "@angular-eslint": angular,
+        },
+        rules: {
+            "@typescript-eslint/naming-convention": [
+                "error",
+                {selector: "class", format: ["PascalCase"]},
+            ],
+
+            curly: "error",
+            eqeqeq: ["error", "always", {null: "ignore"}],
+            "guard-for-in": "error",
+            "max-len": ["error", {code: 140}],
+            "no-bitwise": "error",
+            "no-caller": "error",
+            "no-console": "off",
+            "no-debugger": "error",
+            "no-empty": "error",
+            "no-eval": "error",
+            "no-fallthrough": "error",
+            "no-trailing-spaces": "error",
+            "no-unused-expressions": "error",
+            "no-unused-labels": "error",
+            "no-var": "error",
+            quotes: ["error", "single"],
+            radix: "error",
+            semi: ["error", "always"],
+            "spaced-comment": "error",
+            "brace-style": ["error", "1tbs", {allowSingleLine: true}],
+
+            "@angular-eslint/directive-selector": [
+                "error",
+                {type: "attribute", prefix: "app", style: "camelCase"},
+            ],
+            "@angular-eslint/component-selector": [
+                "error",
+                {type: "element", prefix: "app", style: "kebab-case"},
+            ],
+            "@angular-eslint/no-output-rename": "error",
+            "@angular-eslint/use-pipe-transform-interface": "error",
+            "@angular-eslint/component-class-suffix": "error",
+            "@angular-eslint/directive-class-suffix": "error",
+        },
+    },
+];
diff --git a/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.css b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.css
new file mode 100644
index 0000000..d6c3c5b
--- /dev/null
+++ b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.css
@@ -0,0 +1,76 @@
+.dialog-title {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  font-size: 1.15rem;
+  font-weight: 600;
+  margin: 0;
+  padding: 8px 0 12px;
+  border-bottom: 1px solid #e5e7eb;
+}
+
+.info-section {
+  padding: 12px 0 4px;
+}
+.info-row {
+  display: grid;
+  grid-template-columns: 160px 1fr;
+  gap: 12px;
+  margin-bottom: 12px;
+  align-items: start;
+}
+.info-label {
+  color: #555;
+}
+.info-value {
+  word-break: break-word;
+}
+
+.small {
+  font-size: 0.85em;
+  color: #777;
+  margin-left: 6px;
+}
+
+.samples-container {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.sample-item {
+  background-color: #f9fafb;
+  padding: 10px 12px;
+  border-radius: 8px;
+  border-left: 4px solid #1976d2;
+}
+.sample-desc {
+  margin-bottom: 6px;
+  color: #1976d2;
+  font-weight: 600;
+}
+.sample-value {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
+  font-size: 0.92em;
+  background-color: #fff;
+  padding: 8px;
+  border-radius: 6px;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+
+.no-samples {
+  color: #666; 
+}
+.no-samples a {
+  color: #337ab7;
+  text-decoration: none;
+}
+.no-samples a:hover {
+  text-decoration: underline;
+}
+
+.close-button {
+  background-color: #1976d2 !important;
+  color: white !important;
+  border-radius: 5px !important;
+}
\ No newline at end of file
diff --git a/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.html b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.html
new file mode 100644
index 0000000..5dbab76
--- /dev/null
+++ b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.html
@@ -0,0 +1,66 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<h3 mat-dialog-title class="dialog-title">
+  {{ data?.shortDesc }}
+</h3>
+
+<mat-dialog-content>
+  <div class="info-section">
+    <div class="info-row">
+      <div class="info-label"><strong>Knox Service Name</strong></div>
+      <div class="info-value">
+        {{ data?.serviceName }}
+        <span class="small" *ngIf="data?.version">(v{{ data?.version }})</span>
+      </div>
+    </div>
+
+    <div class="info-row">
+      <div class="info-label"><strong>Description</strong></div>
+      <div class="info-value">{{ data?.description }}</div>
+    </div>
+
+    <div class="info-row">
+      <div class="info-label"><strong>Sample(s)</strong></div>
+      <div class="info-value">
+        <ng-container *ngIf="data.samples?.sample?.length > 0; else noSamples">
+          <div class="samples-container">
+            <div *ngFor="let sample of data.samples.sample" class="sample-item">
+              <div class="sample-desc"><strong>{{ sample.description }}</strong></div>
+              <div class="sample-value">{{ sample.value }}</div>
+            </div>
+          </div>
+        </ng-container>
+
+        <ng-template #noSamples>
+          <div class="no-samples">
+            <p><strong>No samples found in service metadata.</strong></p>
+            <p>
+              Check the service documentation:
+              <a [href]="data.serviceUrls[0]" target="_blank">
+                {{ data.serviceUrls[0] }}
+              </a>
+            </p>
+          </div>
+        </ng-template>
+      </div>
+    </div>
+  </div>
+</mat-dialog-content>
+
+<mat-dialog-actions align="end">
+  <button mat-flat-button mat-dialog-close class="close-button">
+    Close
+  </button>
+</mat-dialog-actions>
\ No newline at end of file
diff --git a/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.ts b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.ts
new file mode 100644
index 0000000..3ad82fb
--- /dev/null
+++ b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, Inject } from '@angular/core';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { MatListModule } from '@angular/material/list';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-apiservice-dialog',
+  imports: [MatDialogModule, MatButtonModule, MatGridListModule, MatListModule, CommonModule],
+  templateUrl: './apiservice-dialog.component.html',
+  styleUrl: './apiservice-dialog.component.css'
+})
+export class ApiserviceDialogComponent {
+  constructor(
+    @Inject(MAT_DIALOG_DATA) public data: any,
+    public dialogRef: MatDialogRef<ApiserviceDialogComponent>
+  ) {
+  }
+}
diff --git a/knox-homepage-ui/home/app/app.module.ts b/knox-homepage-ui/home/app/app.module.ts
index b59429e..9b22cf7 100644
--- a/knox-homepage-ui/home/app/app.module.ts
+++ b/knox-homepage-ui/home/app/app.module.ts
@@ -14,45 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {NgModule} from '@angular/core';
-import {DataTableModule} from 'angular2-datatable';
-import {BrowserModule} from '@angular/platform-browser';
-import {HttpClientModule, HttpClientXsrfModule} from '@angular/common/http';
-import {MatGridListModule} from '@angular/material/grid-list';
-import {BsModalModule} from 'ng2-bs3-modal';
-import {Routes, RouterModule}  from '@angular/router';
-import {APP_BASE_HREF} from '@angular/common';
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { RouterModule } from '@angular/router';
 
-import {GeneralProxyInformationComponent} from './generalProxyInformation/general.proxy.information.component';
-import {TopologyInformationsComponent} from './topologies/topology.information.component';
-import {SessionInformationComponent} from './sessionInformation/session.information.component';
-import {SafeHtmlPipe} from './sessionInformation/session.information.component';
-import {HomepageService} from './homepage.service';
+import { HomepageService } from './service/homepage.service';
 
 @NgModule({
-    imports: [BrowserModule,
-        HttpClientModule,
-        HttpClientXsrfModule,
-        DataTableModule,
-        MatGridListModule,
-        BsModalModule,
-        RouterModule.forRoot([])
-    ],
-    declarations: [GeneralProxyInformationComponent,
-                   TopologyInformationsComponent,
-                   SessionInformationComponent,
-                   SafeHtmlPipe
-    ],
-    providers: [HomepageService,
-      {
-        provide: APP_BASE_HREF,
-        useValue: window['base-href']
-      }
-    ],
-    bootstrap: [SessionInformationComponent,
-                GeneralProxyInformationComponent,
-                TopologyInformationsComponent
-    ]
+  imports: [
+    BrowserModule,
+    HttpClientModule,
+    HttpClientXsrfModule,
+    MatGridListModule,
+    RouterModule.forRoot([]),
+  ],
+  providers: [HomepageService]
 })
-export class AppModule {
-}
+export class AppModule {}
diff --git a/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.html b/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.html
index 8299e5c..2f0a4e9 100644
--- a/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.html
+++ b/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.html
@@ -13,7 +13,11 @@
   limitations under the License.
 -->
 <div>
-     <h4 (click)="toggleBoolean('showGeneralProxyInformation')"><span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (this['showGeneralProxyInformation'] ? 'minus' : 'plus')"></span>&nbsp;General Proxy Information</h4>
+     <h4 (click)="toggleBoolean('showGeneralProxyInformation')" class="d-flex align-items-center">
+        <mat-icon *ngIf="this['showGeneralProxyInformation']">remove</mat-icon>
+        <mat-icon *ngIf="!this['showGeneralProxyInformation']">add</mat-icon>
+        General Proxy Information
+    </h4>
 </div>
 <div class="table-responsive" *ngIf="this['showGeneralProxyInformation']">
     <table class="table table-striped table-hover">
diff --git a/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.ts b/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.ts
index 3143ed4..efa6c08 100644
--- a/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.ts
+++ b/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.component.ts
@@ -16,13 +16,16 @@
  */
 import {Component, OnInit} from '@angular/core';
 import {ActivatedRoute} from '@angular/router';
-import {HomepageService} from '../homepage.service';
-import {GeneralProxyInformation} from './general.proxy.information';
+import {HomepageService} from '../service/homepage.service';
+import {GeneralProxyInformation} from '../model/general.proxy.information';
+import { CommonModule } from '@angular/common';
+import { MatIconModule } from '@angular/material/icon';
 
 @Component({
     selector: 'app-general-proxy-information',
     templateUrl: './general.proxy.information.component.html',
-    providers: [HomepageService]
+    providers: [HomepageService],
+    imports: [CommonModule, MatIconModule]
 })
 
 export class GeneralProxyInformationComponent implements OnInit {
diff --git a/knox-homepage-ui/home/app/homepage.service.ts b/knox-homepage-ui/home/app/homepage.service.ts
deleted file mode 100644
index cbad3d5..0000000
--- a/knox-homepage-ui/home/app/homepage.service.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {Injectable} from '@angular/core';
-import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
-import {ActivatedRoute} from '@angular/router';
-import Swal from 'sweetalert2';
-
-import 'rxjs/add/operator/toPromise';
-
-import {GeneralProxyInformation} from './generalProxyInformation/general.proxy.information';
-import {TopologyInformation} from './topologies/topology.information';
-import {SessionInformation} from './sessionInformation/session.information';
-
-@Injectable()
-export class HomepageService {
-    pathParts = window.location.pathname.split('/');
-    topologyContext = '/' + this.pathParts[1] + '/' + this.pathParts[2] + '/';
-    apiUrl = this.topologyContext + 'api/v1/metadata/';
-    sessionUrl = this.topologyContext + 'session/api/v1/sessioninfo';
-    generalProxyInformationUrl = this.apiUrl + 'info';
-    publicCertUrl = this.apiUrl + 'publicCert?type=';
-    topologiesUrl = this.apiUrl + 'topologies';
-
-    constructor(private http: HttpClient, private route: ActivatedRoute) {}
-
-    getGeneralProxyInformation(): Promise<GeneralProxyInformation> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(this.generalProxyInformationUrl, { headers: headers})
-            .toPromise()
-            .then(response => response['generalProxyInfo'] as GeneralProxyInformation)
-            .catch((err: HttpErrorResponse) => {
-                console.debug('HomepageService --> getGeneralProxyInformation() --> '
-                               + this.generalProxyInformationUrl + '\n  error: ' + err.message);
-                if (err.status === 401) {
-                    window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
-                }
-            });
-    }
-
-    getTopologies(): Promise<TopologyInformation[]> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(this.topologiesUrl, { headers: headers})
-            .toPromise()
-            .then(response => response['topologyInformations'].topologyInformation as TopologyInformation[])
-            .catch((err: HttpErrorResponse) => {
-                console.debug('HomepageService --> getTopologies() --> ' + this.topologiesUrl + '\n  error: ' + err.message);
-                if (err.status === 401) {
-                    window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
-                }
-            });
-    }
-
-    getSessionInformation(): Promise<SessionInformation> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(this.sessionUrl, { headers: headers})
-            .toPromise()
-            .then(response => response['sessioninfo'] as SessionInformation)
-            .catch((err: HttpErrorResponse) => {
-                console.debug('HomepageService --> getSessionInformation() --> ' + this.sessionUrl + '\n  error: ' + err.message);
-                if (err.status === 401) {
-                    window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
-                }
-            });
-    }
-
-    logout(logoutUrl): Promise<JSON> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(logoutUrl, { headers: headers})
-            .toPromise()
-            .then(response => response['loggedOut'])
-            .catch((err: HttpErrorResponse) => {
-                console.debug('HomepageService --> logout() --> ' + logoutUrl + '\n  error: ' + err.message);
-                if (err.status === 401) {
-                    window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
-                }
-            });
-    }
-
-    getProfile(profileName): Promise<JSON> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(this.apiUrl + '/profiles/' + profileName, { headers: headers})
-            .toPromise()
-            .then(response => response)
-            .catch((err: HttpErrorResponse) => {
-                console.debug('HomepageService --> getProfile() --> ' + this.apiUrl + '/profiles/'
-                + profileName + '\n  error: ' + err.message);
-                if (err.status === 401) {
-                    window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
-                }
-            });
-    }
-
-    addJsonHeaders(headers: HttpHeaders): HttpHeaders {
-        return this.addCsrfHeaders(headers.append('Accept', 'application/json').append('Content-Type', 'application/json'));
-    }
-
-    addCsrfHeaders(headers: HttpHeaders): HttpHeaders {
-        return this.addXHRHeaders(headers.append('X-XSRF-Header', 'homepage'));
-    }
-
-    addXHRHeaders(headers: HttpHeaders): HttpHeaders {
-        return headers.append('X-Requested-With', 'XMLHttpRequest');
-    }
-
-    private handleError(error: HttpErrorResponse): Promise<any> {
-        Swal.fire({
-            icon: 'error',
-            title: 'Oops!',
-            text: 'Something went wrong!\n' + (error.error ? error.error : error.statusText),
-            confirmButtonColor: '#7cd1f9'
-        });
-        return Promise.reject(error.message || error);
-    }
-
-}
diff --git a/knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.ts b/knox-homepage-ui/home/app/model/general.proxy.information.ts
similarity index 100%
rename from knox-homepage-ui/home/app/generalProxyInformation/general.proxy.information.ts
rename to knox-homepage-ui/home/app/model/general.proxy.information.ts
diff --git a/knox-homepage-ui/home/app/topologies/sample.ts b/knox-homepage-ui/home/app/model/sample.ts
similarity index 100%
rename from knox-homepage-ui/home/app/topologies/sample.ts
rename to knox-homepage-ui/home/app/model/sample.ts
diff --git a/knox-homepage-ui/home/app/topologies/service.ts b/knox-homepage-ui/home/app/model/service.ts
similarity index 100%
rename from knox-homepage-ui/home/app/topologies/service.ts
rename to knox-homepage-ui/home/app/model/service.ts
diff --git a/knox-homepage-ui/home/app/sessionInformation/session.information.ts b/knox-homepage-ui/home/app/model/session.information.ts
similarity index 100%
rename from knox-homepage-ui/home/app/sessionInformation/session.information.ts
rename to knox-homepage-ui/home/app/model/session.information.ts
diff --git a/knox-homepage-ui/home/app/topologies/topology.information.ts b/knox-homepage-ui/home/app/model/topology.information.ts
similarity index 89%
rename from knox-homepage-ui/home/app/topologies/topology.information.ts
rename to knox-homepage-ui/home/app/model/topology.information.ts
index dabd4c8..e2871f8 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.ts
+++ b/knox-homepage-ui/home/app/model/topology.information.ts
@@ -20,6 +20,10 @@
     topology: string;
     pinned: boolean;
     apiServicesViewVersion: string;
-    apiServices: Service[];
-    uiServices: Service[];
+    apiServices: {
+        service: Service[];
+    };
+    uiServices: {
+        service: Service[];
+    };
 }
diff --git a/knox-homepage-ui/home/app/service/homepage.service.ts b/knox-homepage-ui/home/app/service/homepage.service.ts
new file mode 100644
index 0000000..b4ef43d
--- /dev/null
+++ b/knox-homepage-ui/home/app/service/homepage.service.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
+import { ActivatedRoute } from '@angular/router';
+import Swal from 'sweetalert2/dist/sweetalert2.esm.all.js';
+import { firstValueFrom } from 'rxjs';
+import { GeneralProxyInformation } from '../model/general.proxy.information';
+import { TopologyInformation } from '../model/topology.information';
+import { SessionInformation } from '../model/session.information';
+
+@Injectable()
+export class HomepageService {
+    pathParts = window.location.pathname.split('/');
+    topologyContext = '/' + this.pathParts[1] + '/' + this.pathParts[2] + '/';
+    apiUrl = this.topologyContext + 'api/v1/metadata/';
+    sessionUrl = this.topologyContext + 'session/api/v1/sessioninfo';
+    generalProxyInformationUrl = this.apiUrl + 'info';
+    publicCertUrl = this.apiUrl + 'publicCert?type=';
+    topologiesUrl = this.apiUrl + 'topologies';
+
+    constructor(private http: HttpClient, private route: ActivatedRoute) { }
+
+    getGeneralProxyInformation(): Promise<GeneralProxyInformation> {
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(this.generalProxyInformationUrl, { headers }))
+            .then(resp => resp['generalProxyInfo'] as GeneralProxyInformation)
+            .catch(err => this.handleError(err));
+    }
+
+    getTopologies(): Promise<TopologyInformation[]> {
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(this.topologiesUrl, { headers }))
+            .then(resp => resp['topologyInformations'].topologyInformation as TopologyInformation[])
+            .catch(err => this.handleError(err));
+    }
+
+    getSessionInformation(): Promise<SessionInformation> {
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(this.sessionUrl, { headers }))
+            .then(resp => resp['sessioninfo'] as SessionInformation)
+            .catch(err => this.handleError(err,));
+    }
+
+    logout(logoutUrl: string): Promise<JSON> {
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(logoutUrl, { headers }))
+            .then(resp => resp['loggedOut'])
+            .catch(err => this.handleError(err));
+    }
+
+    getProfile(profileName: string): Promise<JSON> {
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        let url = `${this.apiUrl}/profiles/${profileName}`;
+        return firstValueFrom(this.http.get(url, { headers }))
+            .catch(err => this.handleError(err));
+    }
+
+    addJsonHeaders(headers: HttpHeaders): HttpHeaders {
+        return this.addCsrfHeaders(headers.append('Accept', 'application/json').append('Content-Type', 'application/json'));
+    }
+
+    addCsrfHeaders(headers: HttpHeaders): HttpHeaders {
+        return this.addXHRHeaders(headers.append('X-XSRF-Header', 'homepage'));
+    }
+
+    addXHRHeaders(headers: HttpHeaders): HttpHeaders {
+        return headers.append('X-Requested-With', 'XMLHttpRequest');
+    }
+
+    private handleError(error: HttpErrorResponse): Promise<any> {
+        void Swal.fire({
+            icon: 'error',
+            title: 'Oops!',
+            text: `Something went wrong!\n${error.error ? error.error : error.statusText}`,
+            confirmButtonColor: '#7cd1f9'
+        });
+
+        return Promise.reject(error.message || error);
+    }
+
+
+}
diff --git a/knox-homepage-ui/home/app/sessionInformation/session.information.component.ts b/knox-homepage-ui/home/app/sessionInformation/session.information.component.ts
index 0f7b63d..3c148da 100644
--- a/knox-homepage-ui/home/app/sessionInformation/session.information.component.ts
+++ b/knox-homepage-ui/home/app/sessionInformation/session.information.component.ts
@@ -14,24 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit, Pipe, PipeTransform} from '@angular/core';
-import {DomSanitizer} from '@angular/platform-browser';
-import {HomepageService} from '../homepage.service';
-import {SessionInformation} from './session.information';
-
-@Pipe({ name: 'safeHtml' })
-export class SafeHtmlPipe implements PipeTransform {
-  constructor(private sanitizer: DomSanitizer) {}
-
-  transform(value) {
-    return this.sanitizer.bypassSecurityTrustHtml(value);
-  }
-}
+import {Component, OnInit} from '@angular/core';
+import {HomepageService} from '../service/homepage.service';
+import {SessionInformation} from '../model/session.information';
+import { CommonModule } from '@angular/common';
+import { SafeHtmlPipe } from '../util/safehtml';
 
 @Component({
     selector: 'app-session-information',
     templateUrl: './session.information.component.html',
-    providers: [HomepageService]
+    providers: [HomepageService],
+    imports: [CommonModule, SafeHtmlPipe]
 })
 
 export class SessionInformationComponent implements OnInit {
diff --git a/knox-homepage-ui/home/app/topologies/topology.information.component.css b/knox-homepage-ui/home/app/topologies/topology.information.component.css
index 5d2f9cd..9e4a515 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.component.css
+++ b/knox-homepage-ui/home/app/topologies/topology.information.component.css
@@ -29,12 +29,70 @@
   word-wrap: break-word;
 }
 
+.tile-longDesc h3 {
+  display: -webkit-box;
+  line-clamp: 5;
+  -webkit-line-clamp: 5;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
 .tile-shortDesc:hover + .tile-longDesc {
   opacity: 1;
   transition: all 0.3s linear;
   visibility: visible;
 }
 
-/deep/ .groupServiceInformationModal {
-    width: 80%;
+::ng-deep .mat-mdc-tooltip {
+    background-color: lightgray;
+}
+
+::ng-deep .mat-mdc-paginator {
+    padding: 8px 12px;
+    min-height: 48px;
+    font-size: 13px;
+}
+
+::ng-deep .mat-mdc-paginator .mat-mdc-icon-button {
+    width: 36px;
+    height: 36px;
+}
+
+::ng-deep .mat-mdc-paginator .mat-mdc-select {
+    margin: 0 8px;
+    min-width: 56px;
+}
+
+::ng-deep div.mat-mdc-select-panel {
+  background-color: white !important;
+}
+
+::ng-deep .mat-mdc-paginator .mat-mdc-select .mat-mdc-select-value {
+    color: #333;
+    font-size: 13px;
+}
+
+::ng-deep .mat-mdc-paginator .mat-mdc-select .mat-mdc-select-arrow {
+    color: #666;
+}
+
+::ng-deep .mat-mdc-paginator .mat-mdc-select-trigger {
+    padding: 4px 8px;
+    border-radius: 4px;
+    border: 1px solid #ddd;
+}
+
+::ng-deep .mdc-notched-outline > * {
+  border: none !important;
+}
+
+.reduce-icon-size {
+   transform: scale(0.65);
+}
+
+hr {
+    margin-top: 15px;
+    margin-bottom: 15px;
+    border: 0;
+    border-top: 1px solid #868686;
 }
\ No newline at end of file
diff --git a/knox-homepage-ui/home/app/topologies/topology.information.component.html b/knox-homepage-ui/home/app/topologies/topology.information.component.html
index 5109454..9293f22 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.component.html
+++ b/knox-homepage-ui/home/app/topologies/topology.information.component.html
@@ -12,195 +12,140 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<hr/>
+<hr />
 <div>
-     <h4 (click)="toggleBoolean('showTopologies')"><span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (this['showTopologies'] ? 'minus' : 'plus')"></span>&nbsp;Topologies</h4>
+    <h4 (click)="toggleBoolean('showTopologies')" class="d-flex align-items-center">
+        <mat-icon *ngIf="this['showTopologies']">remove</mat-icon>
+        <mat-icon *ngIf="!this['showTopologies']">add</mat-icon>Topologies
+    </h4>
 </div>
 <div *ngIf="this['showTopologies']">
-<ng-container *ngFor="let topology of topologies">
-    <div>
-      <span [class]="'clickable inline-glyph
-      glyhpicon glyphicon-' + (this['showTopology_' + topology.topology] ? 'minus' : 'plus')"
-      (click)="toggleBoolean('showTopology_' + topology.topology)"></span>
-      <span (click)="toggleBoolean('showTopology_' + topology.topology)"><strong>{{topology.topology}}</strong></span>
-      <span class="inline-glyph glyphicon glyphicon-pushpin btn btn-xs" *ngIf="topology.pinned"
-            title="You may unpin this topology in gateway-site.xml by removing it from the list declared in 'knox.homepage.pinned.topologies'"
-            data-toggle="tooltip"></span>
-      <span class="inline-glyph glyphicon glyphicon-cog btn btn-xs" *ngIf="!topology.pinned"
-            title="You may pin this topology in gateway-site.xml using the 'knox.homepage.pinned.topologies' property"
-            data-toggle="tooltip"></span>
-    </div>
+    <ng-container *ngFor="let topology of topologies">
+        <div class="d-flex align-items-center" (click)="toggleBoolean('showTopology_' + topology.topology)">
+            <mat-icon *ngIf="this['showTopology_' + topology.topology]" class="reduce-icon-size">remove</mat-icon>
+            <mat-icon *ngIf="!this['showTopology_' + topology.topology]" class="reduce-icon-size">add</mat-icon>
+            <strong>{{topology.topology}}</strong>
 
-    <div class="table-responsive" *ngIf="this['showTopology_' + topology.topology]">
+            <mat-icon *ngIf="topology.pinned"
+                matTooltip="You may unpin this topology in gateway-site.xml by removing it from the list declared in 'knox.homepage.pinned.topologies'"
+                class="reduce-icon-size">push_pin</mat-icon>
+            <mat-icon *ngIf="!topology.pinned"
+                matTooltip="You may pin this topology in gateway-site.xml using the 'knox.homepage.pinned.topologies' property"
+                class="reduce-icon-size">settings</mat-icon>
+        </div>
 
-        <h5 *ngIf="topology.uiServices.service.length > 0">UI Services</h5>
+        <div class="table-responsive ps-3" *ngIf="this['showTopology_' + topology.topology]">
 
-        <!-- UI services -->
-        <mat-grid-list cols="4" rowHeight="130px">
-            <mat-grid-tile *ngFor="let service of topology.uiServices.service" [colspan]="1" [rowspan]="1">
+            <h5 *ngIf="topology.uiServices.service.length > 0" class="pt-2">UI Services</h5>
 
-                <!-- Services with 1 service URL -->
-                <div *ngIf="service.serviceUrls.length === 1">
-                    <a href="{{service.serviceUrls[0]}}" target="_blank" *ngIf="!this['enableServiceText_' + service.serviceName.toLowerCase()]">
-                        <img src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" height="50px" (error)="enableServiceText('enableServiceText_' + service.serviceName.toLowerCase())"/>
-                    </a>
-                    <a href="{{service.serviceUrls[0]}}" target="_blank" *ngIf="this['enableServiceText_' + service.serviceName.toLowerCase()]">
+            <!-- UI services -->
+            <mat-grid-list cols="4" rowHeight="130px" *ngIf="topology.uiServices.service.length > 0">
+                <mat-grid-tile *ngFor="let service of topology.uiServices.service" [colspan]="1" [rowspan]="1" (click)="service.serviceUrls.length > 1
+            ? openGroupServiceDialog(service)
+            : openSingleService(service.serviceUrls[0])">
+
+                    <!-- Services with 1 service URL -->
+                    <div *ngIf="service.serviceUrls.length === 1">
+                        <span *ngIf="!this['enableServiceText_' + service.serviceName.toLowerCase()]">
+                            <img src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" height="50px"
+                                (error)="enableServiceText('enableServiceText_' + service.serviceName.toLowerCase())" />
+                        </span>
+                        <span *ngIf="this['enableServiceText_' + service.serviceName.toLowerCase()]">
+                            {{service.shortDesc}}
+                        </span>
+                        <mat-grid-tile-footer class="tile-shortDesc">
+                            <h3>{{service.shortDesc}} <span class="small"
+                                    *ngIf="service.version">(v{{service.version}})</span></h3>
+                        </mat-grid-tile-footer>
+                        <mat-grid-tile-footer class="tile-longDesc" style="height:100px;">
+                            <h3>{{service.description}}</h3>
+                        </mat-grid-tile-footer>
+                    </div>
+
+                    <!-- Services with more than 1 service URL -->
+                    <div *ngIf="service.serviceUrls.length > 1" (click)="openGroupServiceDialog(service)">
+                        <img src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" height="50px"
+                            (error)="enableServiceText('enableServiceText_' + service.serviceName.toLowerCase())" />
+                        <mat-grid-tile-footer class="tile-shortDesc">
+                            <h3>{{service.shortDesc}} ({{service.serviceUrls.length}})</h3>
+                        </mat-grid-tile-footer>
+                    </div>
+
+                </mat-grid-tile>
+            </mat-grid-list>
+
+            <!-- API services -->
+
+            <h5 *ngIf="topology.apiServices.service.length > 0" class="pt-2">API Services</h5>
+
+            <div *ngIf="topology.apiServicesViewVersion === 'v1'">
+
+                <!-- Header -->
+                <ng-container *ngIf="topology.apiServices.service.length === 0">
+                    <h5 class="pt-2">No API services found</h5>
+                </ng-container>
+
+                <!-- Material Table -->
+                <table mat-table [dataSource]="getApiServicesDataSource(topology)" class="mat-elevation-z8"
+                    *ngIf="topology.apiServices.service.length > 0">
+
+                    <!-- Description Column -->
+                    <ng-container matColumnDef="description">
+                        <th mat-header-cell *matHeaderCellDef> Description </th>
+                        <td mat-cell *matCellDef="let service">
+                            <div class="d-flex align-items-center">
+                                <mat-icon [matTooltip]="service.description" class="reduce-icon-size">info</mat-icon>
+                                {{ service.shortDesc }}
+                                <span class="small" *ngIf="service.version">(v{{ service.version }})</span>
+                            </div>
+                        </td>
+                    </ng-container>
+
+                    <!-- URLs Column -->
+                    <ng-container matColumnDef="urls">
+                        <th mat-header-cell *matHeaderCellDef> URLs </th>
+                        <td mat-cell *matCellDef="let service">
+                            <ng-container *ngFor="let url of service.serviceUrls; let i = index">
+                                <a [href]="url" target="_blank">{{ url }}</a>
+                                <br *ngIf="service.serviceUrls.length > 1 && i < service.serviceUrls.length - 1" />
+                            </ng-container>
+                        </td>
+                    </ng-container>
+
+                    <!-- Fixed: Use displayedApiColumns instead of displayedColumns -->
+                    <tr mat-header-row *matHeaderRowDef="displayedApiColumns"></tr>
+                    <tr mat-row *matRowDef="let row; columns: displayedApiColumns;"></tr>
+
+                </table>
+
+                <!-- Paginator with proper data source binding -->
+                <mat-paginator [pageSizeOptions]="[5, 10, 15]" [pageSize]="5" showFirstLastButtons
+                    [length]="topology.apiServices.service.length"
+                    [aria-label]="'Pagination for ' + topology.topology + ' API services'"
+                    *ngIf="topology.apiServices.service.length > 0">
+                </mat-paginator>
+            </div>
+
+            <mat-grid-list *ngIf="topology.apiServicesViewVersion === 'v2'" cols="4" rowHeight="130px">
+                <mat-grid-tile *ngFor="let service of topology.apiServices.service" [colspan]="1" [rowspan]="1"
+                    (click)="openApiServiceDialog(service)">
+                    <span *ngIf="!this['enableServiceText_' + service.serviceName.toLowerCase()]">
+                        <img src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" height="50px"
+                            (error)="enableServiceText('enableServiceText_' + service.serviceName.toLowerCase())" />
+                    </span>
+                    <span *ngIf="this['enableServiceText_' + service.serviceName.toLowerCase()]">
                         {{service.shortDesc}}
-                    </a>
+                    </span>
                     <mat-grid-tile-footer class="tile-shortDesc">
-                        <h3>{{service.shortDesc}} <span class="small" *ngIf="service.version">(v{{service.version}})</span></h3>
+                        <h3>{{service.shortDesc}} <span class="small"
+                                *ngIf="service.version">(v{{service.version}})</span></h3>
                     </mat-grid-tile-footer>
                     <mat-grid-tile-footer class="tile-longDesc" style="height:100px;">
                         <h3>{{service.description}}</h3>
                     </mat-grid-tile-footer>
-                </div>
-
-                <!-- Services with more than 1 service URL -->
-                <div *ngIf="service.serviceUrls.length > 1" (click)="openGroupServiceInformationModal(service)">
-                    <img src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" height="50px" (error)="enableServiceText('enableServiceText_' + service.serviceName.toLowerCase())"/>
-                    <mat-grid-tile-footer class="tile-shortDesc">
-                        <h3>{{service.shortDesc}} ({{service.serviceUrls.length}})</h3>
-                    </mat-grid-tile-footer>
-                </div>
-
-            </mat-grid-tile>
-        </mat-grid-list>
-
-        <!-- API services -->
-
-        <h5 *ngIf="topology.apiServices.service.length > 0">API Services</h5>
-
-        <table *ngIf="topology.apiServicesViewVersion === 'v1'" class="table table-hover" [mfData]="topology.apiServices.service" #api="mfDataTable" [mfRowsOnPage]="5">
-            <colgroup>
-                <col width="30%">
-                <col width="70%">
-            </colgroup>
-            <thead>
-                <tr *ngIf="topology.apiServices.service.length === 0"><th colspan="2">No API services found</th></tr>
-                <tr *ngIf="topology.apiServices.service.length > 0"><th colspan="2">API services</th></tr>
-            </thead>
-            <tbody>
-                <tr *ngFor="let service of api.data">
-                    <td>
-                        <span class="inline-glyph glyphicon glyphicon-info-sign btn btn-xs"
-                        title="{{service.description}}"
-                        data-toggle="tooltip"></span>
-                        {{service.shortDesc}} <span class="small" *ngIf="service.version">(v{{service.version}})</span>
-                    </td>
-                    <td>
-                        <ng-container *ngFor="let serviceUrl of service.serviceUrls">
-                            <a href="{{serviceUrl}}">{{serviceUrl}}</a>
-                            <br *ngIf="service.serviceUrls.length > 1" />
-                        </ng-container>
-                    </td>
-                </tr>
-            </tbody>
-            <tfoot>
-                <tr>
-                    <td colspan="4">
-                      <mfBootstrapPaginator [rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
-                    </td>
-                </tr>
-            </tfoot>
-        </table>
-
-        <mat-grid-list *ngIf="topology.apiServicesViewVersion === 'v2'" cols="4" rowHeight="130px">
-            <mat-grid-tile *ngFor="let service of topology.apiServices.service" [colspan]="1" [rowspan]="1">
-                <span *ngIf="!this['enableServiceText_' + service.serviceName.toLowerCase()]" (click)="openApiServiceInformationModal(service)">
-                    <img src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" height="50px" (error)="enableServiceText('enableServiceText_' + service.serviceName.toLowerCase())"/>
-                </span>
-                <span *ngIf="this['enableServiceText_' + service.serviceName.toLowerCase()]" (click)="openApiServiceInformationModal(service)">
-                    {{service.shortDesc}}
-                </span>
-                <mat-grid-tile-footer class="tile-shortDesc">
-                    <h3>{{service.shortDesc}} <span class="small" *ngIf="service.version">(v{{service.version}})</span></h3>
-                </mat-grid-tile-footer>
-                <mat-grid-tile-footer class="tile-longDesc" style="height:100px;">
-                    <h3>{{service.description}}</h3>
-                </mat-grid-tile-footer>
-            </mat-grid-tile>
-        </mat-grid-list>
-    </div>
-</ng-container>
-<hr />
-</div>
-
-<bs-modal #apiServiceInformationModal>
-    <bs-modal-header [showDismiss]="true" *ngIf="selectedApiService">
-        <h4 class="modal-title">{{selectedApiService.shortDesc}}</h4>
-    </bs-modal-header>
-    <bs-modal-body *ngIf="selectedApiService">
-        <div class="panel panel-default table-responsive">
-            <table class="table table-sm">
-                <colgroup>
-                   <col width="25%">
-                   <col width="75%">
-               </colgroup>
-               <tr>
-                   <td style="font-weight: bold;">Knox Service Name</td>
-                   <td>{{selectedApiService.serviceName}} <span class="small" *ngIf="selectedApiService.version">(v{{selectedApiService.version}})</span></td>
-               </tr>
-               <tr>
-                   <td style="font-weight: bold;">Description</td>
-                   <td>{{selectedApiService.description}}</td>
-               </tr>
-               <tr>
-                   <td style="font-weight: bold;">Sample(s)</td>
-                   <td>
-                     <table class="table table-sm">
-                       <div *ngIf="selectedApiService.samples && selectedApiService.samples.sample.length > 0">
-                         <ng-container *ngFor="let sample of selectedApiService.samples.sample">
-                           <tr><td style="font-weight: bold;">{{sample.description}}</td></tr>
-                           <tr style="margin-bottom: 15px"><td>&nbsp;&nbsp;{{sample.value}}</tr>
-                         </ng-container>
-                       </div>
-                       <div *ngIf="!selectedApiService.samples || selectedApiService.samples.sample.length < 1">
-                         <tr><td style="font-weight: bold;">There is no any sample found in service metadata</td></tr>
-                         <tr>
-                           <td>&nbsp;You may check out the service's documentation and find out how to use its REST API. The service's URL is <a href="{{selectedApiService.serviceUrls[0]}}" target="_blank">{{selectedApiService.serviceUrls[0]}}</a></td>
-                         </tr>
-                       </div>
-                     </table>
-                   </td>
-               </tr>
-           </table>
-       </div>
-       
-    </bs-modal-body>
-    <bs-modal-footer>
-        <button type="button" class="btn btn-primary btn-sm" (click)="apiServiceInformationModal.close()">Close</button>
-    </bs-modal-footer>
-</bs-modal>
-
-
-<bs-modal #groupServiceInformationModal cssClass="groupServiceInformationModal" [animation]="true">
-    <bs-modal-header [showDismiss]="true" *ngIf="selectedGroupService">
-        <h4 class="modal-title">{{selectedGroupService.shortDesc}} <span class="small" *ngIf="selectedGroupService.version">(v{{selectedGroupService.version}})</span></h4>
-    </bs-modal-header>
-     <bs-modal-body *ngIf="selectedGroupService">
-         <span>
-             {{selectedGroupService.description}}
-         </span>
-         <br/><br/>
-         <span align="center">
-            <input type="text" placeholder="Search by hostname, port..." (input)="filterServiceUrls($event.target.value)" style="width: 50%">
-         </span>
-
-        <mat-grid-list cols="4" rowHeight="130px">
-            <mat-grid-tile *ngFor="let serviceUrl of filteredServiceUrls" [colspan]="1" [rowspan]="1">
-                <a href="{{serviceUrl}}" target="_blank" *ngIf="!this['enableServiceText_' + selectedGroupService.serviceName.toLowerCase()]">
-                    <img src="assets/service-logos/{{selectedGroupService.serviceName.toLowerCase()}}.png" height="50px" (error)="enableServiceText('enableServiceText_' + selectedGroupService.serviceName.toLowerCase())"/>
-                </a>
-                <a href="{{serviceUrl}}" target="_blank" *ngIf="this['enableServiceText_' + selectedGroupService.serviceName.toLowerCase()]">
-                    {{selectedGroupService.shortDesc}}
-                </a>
-                <mat-grid-tile-footer class="tile-shortDesc">
-                    <h3>{{selectedGroupService.shortDesc}} {{getServiceUrlHostAndPort(serviceUrl)}}</h3>
-                </mat-grid-tile-footer>
-            </mat-grid-tile>
-        </mat-grid-list>
-     </bs-modal-body>
-    <bs-modal-footer>
-        <button type="button" class="btn btn-primary btn-sm" (click)="groupServiceInformationModal.close()">Close</button>
-    </bs-modal-footer>
-</bs-modal>
+                </mat-grid-tile>
+            </mat-grid-list>
+        </div>
+    </ng-container>
+    <hr />
+</div>
\ No newline at end of file
diff --git a/knox-homepage-ui/home/app/topologies/topology.information.component.ts b/knox-homepage-ui/home/app/topologies/topology.information.component.ts
index bbb1876..4ede4b0 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.component.ts
+++ b/knox-homepage-ui/home/app/topologies/topology.information.component.ts
@@ -14,41 +14,111 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit, ViewChild} from '@angular/core';
-import {ActivatedRoute} from '@angular/router';
-import {MatGridListModule} from '@angular/material/grid-list';
-import {HomepageService} from '../homepage.service';
-import {TopologyInformation} from './topology.information';
-import {Service} from './service';
-import {BsModalComponent} from 'ng2-bs3-modal';
-
+import { Component, OnInit, ViewChildren, QueryList } from '@angular/core';
+import { MatTableModule, MatTableDataSource } from '@angular/material/table';
+import { MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
+import { MatDialog } from '@angular/material/dialog';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { ActivatedRoute } from '@angular/router';
+import { HomepageService } from '../service/homepage.service';
+import { TopologyInformation } from '../model/topology.information';
+import { Service } from '../model/service';
+import { CommonModule } from '@angular/common';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { ApiserviceDialogComponent } from '../apiservice-dialog/apiservice-dialog.component';
+import { UiserviceDialogComponent } from '../uiservice-dialog/uiservice-dialog.component';
 @Component({
     selector: 'app-topologies-information',
     templateUrl: './topology.information.component.html',
     styleUrls: ['./topology.information.component.css'],
-    providers: [HomepageService]
+    providers: [HomepageService],
+    imports: [CommonModule, MatGridListModule, MatTooltipModule, MatIconModule, MatTableModule, MatPaginatorModule]
 })
 
 export class TopologyInformationsComponent implements OnInit {
 
-    @ViewChild('apiServiceInformationModal')
-    apiServiceInformationModal: BsModalComponent;
-
-    @ViewChild('groupServiceInformationModal')
-    groupServiceInformationModal: BsModalComponent;
-
     topologies: TopologyInformation[];
     desiredTopologies: string[];
     selectedApiService: Service;
     selectedGroupService: Service;
     filteredServiceUrls: string[];
 
+    displayedApiColumns: string[] = ['description', 'urls'];
+    private topologyDataSources = new Map<string, MatTableDataSource<Service>>();
+    @ViewChildren(MatPaginator) allPaginators!: QueryList<MatPaginator>;
+
+    constructor(
+        private homepageService: HomepageService,
+        private route: ActivatedRoute,
+        private dialog: MatDialog
+    ) {
+        this['showTopologies'] = true;
+    }
+
     setTopologies(topologies: TopologyInformation[]) {
         this.topologies = topologies;
         this.filterTopologies();
+
+        this.topologyDataSources.clear();
+        this.createDataSources();
+
         for (let topology of topologies) {
             this['showTopology_' + topology.topology] = topology.pinned;
         }
+
+        setTimeout(() => this.linkPaginatorsToData(), 50);
+    }
+
+    private createDataSources() {
+        if (!this.topologies) {
+            return;
+        }
+
+        this.topologies.forEach(topology => {
+            const dataSource = new MatTableDataSource<Service>();
+            if (topology.apiServices && topology.apiServices.service) {
+                dataSource.data = topology.apiServices.service;
+            }
+            this.topologyDataSources.set(topology.topology, dataSource);
+        });
+    }
+
+    ngAfterViewInit() {
+        this.linkPaginatorsToData();
+
+        this.allPaginators.changes.subscribe(() => {
+            setTimeout(() => this.linkPaginatorsToData(), 50);
+        });
+    }
+
+    private linkPaginatorsToData() {
+        if (!this.topologies || !this.allPaginators) {
+            return;
+        }
+
+        let currentPaginatorIndex = 0;
+
+        this.topologies.forEach(topology => {
+            const shouldShowTable = topology.apiServicesViewVersion === 'v1' &&
+                topology.apiServices?.service &&
+                topology.apiServices.service.length > 0 &&
+                this['showTopology_' + topology.topology];
+
+            if (shouldShowTable) {
+                const paginatorInstance = this.allPaginators.toArray()[currentPaginatorIndex];
+                const dataSource = this.topologyDataSources.get(topology.topology);
+
+                if (paginatorInstance && dataSource) {
+                    dataSource.paginator = paginatorInstance;
+                    currentPaginatorIndex++;
+                }
+            }
+        });
+    }
+
+    getApiServicesDataSource(topology: TopologyInformation): MatTableDataSource<Service> {
+        return this.topologyDataSources.get(topology.topology);
     }
 
     toggleBoolean(propertyName: string) {
@@ -59,10 +129,6 @@
         this[enableServiceText] = true;
     }
 
-    constructor(private homepageService: HomepageService, private route: ActivatedRoute) {
-        this['showTopologies'] = true;
-    }
-
     ngOnInit(): void {
         console.debug('TopologyInformationsComponent --> ngOnInit()');
         this.homepageService.getTopologies().then(topologies => this.setTopologies(topologies));
@@ -73,64 +139,85 @@
                 this.desiredTopologies = topologiesParam.split(',');
                 this.filterTopologies();
             } else {
-	        	    let profileName = params['profile'];
-	            console.debug('Profile name = ' + profileName);
-	            if (profileName) {
-	            	    console.debug('Fetching profile information...');
-	            	    this.homepageService.getProfile(profileName).then(profile => this.setDesiredTopologiesFromProfile(profile));
-	            }
+                let profileName = params['profile'];
+                console.debug('Profile name = ' + profileName);
+                if (profileName) {
+                    console.debug('Fetching profile information...');
+                    this.homepageService.getProfile(profileName).then(profile => this.setDesiredTopologiesFromProfile(profile));
+                }
             }
         });
     }
 
     setDesiredTopologiesFromProfile(profile: JSON) {
-      let topologiesInProfile = profile['topologies'];
-      if (topologiesInProfile !== '') {
-         this.desiredTopologies = topologiesInProfile.split(',');
-         this.filterTopologies();
-      }
+        let topologiesInProfile = profile['topologies'];
+        if (topologiesInProfile !== '') {
+            this.desiredTopologies = topologiesInProfile.split(',');
+            this.filterTopologies();
+        }
     }
 
     filterTopologies() {
-      if (this.topologies && this.desiredTopologies && this.desiredTopologies.length > 0) {
-      console.debug('Filtering topologies...');
-         let filteredTopologies = [];
-         for (let desiredTopology of this.desiredTopologies) {
-	         for (let topology of this.topologies) {
-	            if (topology.topology === desiredTopology) {
-	                filteredTopologies.push(topology);
-	            }
-	         }
-         }
-         this.topologies = filteredTopologies;
-      }
+        if (this.topologies && this.desiredTopologies && this.desiredTopologies.length > 0) {
+            console.debug('Filtering topologies...');
+            let filteredTopologies = [];
+            for (let desiredTopology of this.desiredTopologies) {
+                for (let topology of this.topologies) {
+                    if (topology.topology === desiredTopology) {
+                        filteredTopologies.push(topology);
+                    }
+                }
+            }
+            this.topologies = filteredTopologies;
+
+        }
     }
 
     openApiServiceInformationModal(apiService: Service) {
         this.selectedApiService = apiService;
-        this.apiServiceInformationModal.open('lg');
     }
 
     openGroupServiceInformationModal(groupService: Service) {
         this.selectedGroupService = groupService;
         this.filteredServiceUrls = this.selectedGroupService.serviceUrls;
-        this.groupServiceInformationModal.open();
+
     }
 
     getServiceUrlHostAndPort(serviceUrl: string): string {
-	    let hostStart = serviceUrl.indexOf('host=');
-        if (hostStart > 0 ) {
+        let hostStart = serviceUrl.indexOf('host=');
+        if (hostStart > 0) {
             return ' - ' + serviceUrl.slice(hostStart).replace('host=', '').replace('&port=', ':')
-                   .replace('https://', '').replace('http://', '');
+                .replace('https://', '').replace('http://', '');
         }
-	    return '';
+        return '';
     }
 
     filterServiceUrls(filterText: string): void {
-	    if (filterText === '') {
-		    this.filteredServiceUrls = this.selectedGroupService.serviceUrls;
-	    } else {
-		    this.filteredServiceUrls = this.selectedGroupService.serviceUrls.filter(serviceUrl => serviceUrl.includes(filterText));
+        if (filterText === '') {
+            this.filteredServiceUrls = this.selectedGroupService.serviceUrls;
+        } else {
+            this.filteredServiceUrls = this.selectedGroupService.serviceUrls.filter(serviceUrl => serviceUrl.includes(filterText));
         }
     }
+
+    openApiServiceDialog(service: any): void {
+        this.dialog.open(ApiserviceDialogComponent, {
+            width: '70vw',
+            maxWidth: 'none',
+            data: service
+        });
+    }
+
+    openGroupServiceDialog(service: any): void {
+        this.dialog.open(UiserviceDialogComponent, {
+            width: '70vw',
+            maxWidth: 'none',
+            data: service
+        });
+    }
+
+    openSingleService(url: string): void {
+        if (!url) { return; }
+        window.open(url, '_blank');
+    }
 }
diff --git a/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.css b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.css
new file mode 100644
index 0000000..4bb1df6
--- /dev/null
+++ b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.css
@@ -0,0 +1,57 @@
+.dialog-title {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  font-size: 1.15rem;
+  font-weight: 600;
+  margin: 0;
+  padding: 8px 0 12px;
+  border-bottom: 1px solid #e5e7eb;
+}
+
+.dialog-content {
+  padding-top: 12px;
+}
+.description {
+  margin: 0 0 8px 0;
+  color: #444;
+}
+
+.search-field {
+  --mdc-outline-color: #d3d3d3; /* light grey */
+}
+
+.grid-container {
+  margin-top: 8px;
+}
+
+mat-grid-tile {
+  border-radius: 8px;
+  overflow: hidden;
+}
+.tile-shortDesc h3 {
+  font-size: 0.95rem;
+  margin: 0;
+}
+
+.fallback-link {
+  display: inline-block;
+  color: #1976d2;
+  text-decoration: none;
+  font-weight: 600;
+  margin-bottom: 6px;
+}
+.fallback-link:hover {
+  text-decoration: underline;
+}
+
+.dialog-actions {
+  padding-top: 8px;
+  border-top: 1px solid #e5e7eb;
+}
+
+.close-button {
+  background-color: #1976d2 !important;
+  color: white !important;
+  border-radius: 5px !important;
+}
\ No newline at end of file
diff --git a/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.html b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.html
new file mode 100644
index 0000000..3600905
--- /dev/null
+++ b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.html
@@ -0,0 +1,53 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+      http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<h2 mat-dialog-title *ngIf="data" class="dialog-title">
+  {{ data.shortDesc }}
+  <span class="small" *ngIf="data.version">(v{{ data.version }})</span>
+</h2>
+
+<mat-dialog-content *ngIf="data" class="dialog-content pt-2">
+  <p>{{ data.description }}</p>
+
+  <mat-form-field appearance="outline" class="search-field">
+    <mat-label>Search by hostname, port...</mat-label>
+    <input matInput (input)="filterServiceUrls($event.target.value)">
+  </mat-form-field>
+
+  <mat-grid-list cols="4" rowHeight="130px" gutterSize="12px" class="grid-container">
+    <mat-grid-tile *ngFor="let serviceUrl of filteredServiceUrls" [colspan]="1" [rowspan]="1">
+      <a [href]="serviceUrl" target="_blank" *ngIf="!enableServiceText">
+        <img
+          [src]="'assets/service-logos/' + data.serviceName.toLowerCase() + '.png'"
+          height="50px"
+          (error)="enableServiceText = true"
+        />
+      </a>
+
+      <a [href]="serviceUrl" target="_blank" *ngIf="enableServiceText">
+        {{ data.shortDesc }}
+      </a>
+
+      <mat-grid-tile-footer class="tile-shortDesc">
+        <h3>{{ data.shortDesc }} {{ getServiceUrlHostAndPort(serviceUrl) }}</h3>
+      </mat-grid-tile-footer>
+    </mat-grid-tile>
+  </mat-grid-list>
+</mat-dialog-content>
+
+<mat-dialog-actions align="end">
+    <button mat-flat-button mat-dialog-close class="close-button">
+    Close
+  </button>
+</mat-dialog-actions>
diff --git a/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.ts b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.ts
new file mode 100644
index 0000000..88042c2
--- /dev/null
+++ b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.ts
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-uiservice-dialog',
+  imports: [MatDialogModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatGridListModule, CommonModule],
+  templateUrl: './uiservice-dialog.component.html',
+  styleUrl: './uiservice-dialog.component.css'
+})
+export class UiserviceDialogComponent {
+  enableServiceText = false;
+  filteredServiceUrls: string[] = [];
+
+  constructor(@Inject(MAT_DIALOG_DATA) public data: any) {
+    this.filteredServiceUrls = [...(data?.serviceUrls || [])];
+  }
+
+  filterServiceUrls(searchTerm: string): void {
+    const term = searchTerm.toLowerCase();
+    this.filteredServiceUrls = (this.data.serviceUrls || []).filter(url =>
+      url.toLowerCase().includes(term)
+    );
+  }
+
+  getServiceUrlHostAndPort(url: string): string {
+    try {
+      const parsedUrl = new URL(url);
+      return `(${parsedUrl.hostname}:${parsedUrl.port || (parsedUrl.protocol === 'https:' ? '443' : '80')})`;
+    } catch (e) {
+      console.error(e);
+      return '';
+    }
+  }
+}
diff --git a/knox-homepage-ui/home/app/topologies/topology.information.ts b/knox-homepage-ui/home/app/util/safehtml.ts
similarity index 70%
copy from knox-homepage-ui/home/app/topologies/topology.information.ts
copy to knox-homepage-ui/home/app/util/safehtml.ts
index dabd4c8..cdf6aef 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.ts
+++ b/knox-homepage-ui/home/app/util/safehtml.ts
@@ -14,12 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Service} from './service';
+import {Pipe, PipeTransform} from '@angular/core';
+import {DomSanitizer} from '@angular/platform-browser';
 
-export class TopologyInformation {
-    topology: string;
-    pinned: boolean;
-    apiServicesViewVersion: string;
-    apiServices: Service[];
-    uiServices: Service[];
+@Pipe({ name: 'safeHtml' })
+export class SafeHtmlPipe implements PipeTransform {
+  constructor(private sanitizer: DomSanitizer) {}
+
+  transform(value) {
+    return this.sanitizer.bypassSecurityTrustHtml(value);
+  }
 }
diff --git a/knox-homepage-ui/home/index.html b/knox-homepage-ui/home/index.html
index 431a388..7ce0fd1 100644
--- a/knox-homepage-ui/home/index.html
+++ b/knox-homepage-ui/home/index.html
@@ -18,43 +18,37 @@
     <meta charset="utf-8">
     <title>Apache Knox Home</title>
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
-    <meta http-equiv="Pragma" content="no-cache">
-    <meta http-equiv="Expires" content="0">
+    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
     
     <link rel="icon" type="image/x-icon" href="favicon.ico">
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <!-- Custom styles for this template -->
-    <link href="assets/sticky-footer.css" rel="stylesheet">
+
 	<script>
 	  window['base-href'] = window.location.pathname;
-	  console.log(window['base-href']);
 	</script>
 </head>
 <body>
-<div class="navbar-wrapper">
-    <div class="container-fluid">
-        <nav class="navbar navbar-inverse navbar-static-top">
-            <div class="container-fluid">
-                <div class="navbar-header">
-                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
-                            data-target="#navbar" aria-expanded="false" aria-controls="navbar">
-                        <span class="sr-only">Toggle navigation</span>
-                        <span class="icon-bar"></span>
-                        <span class="icon-bar"></span>
-                        <span class="icon-bar"></span>
-                    </button>
-                    <a class="navbar-brand" href="#"> <img style="max-width:200px; margin-top: -9px;" src="assets/knox-logo-transparent.gif" alt="Apache Knox Home"></a>
-                </div>
-                <app-session-information></app-session-information>
-            </div>
-        </nav>
-    </div>
+  <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
+    <div class="container-fluid align-items-start">
+      <a class="navbar-brand p-0 me-3 d-flex" href="#">
+        <img src="assets/knox-logo-transparent.gif" alt="Apache Knox Home" style="max-width:200px; margin-top:-5px;">
+      </a>
 
-    <div class="container-fluid">
-        <app-general-proxy-information></app-general-proxy-information>
-        <app-topologies-information></app-topologies-information>
+      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent"
+              aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
+        <span class="navbar-toggler-icon"></span>
+      </button>
+
+      <div class="collapse navbar-collapse" id="navbarContent">
+         <div class="ms-auto d-flex flex-column pt-0">
+          <app-session-information></app-session-information>
+        </div>
+      </div>
     </div>
-</div>
+  </nav>
+  <div class="container-fluid mt-4">
+    <app-general-proxy-information></app-general-proxy-information>
+    <app-topologies-information></app-topologies-information>
+  </div>
 </body>
 </html>
diff --git a/knox-homepage-ui/home/main.ts b/knox-homepage-ui/home/main.ts
index be5f383..b16aaa4 100644
--- a/knox-homepage-ui/home/main.ts
+++ b/knox-homepage-ui/home/main.ts
@@ -14,15 +14,41 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import { enableProdMode, importProvidersFrom } from '@angular/core';
+import { environment } from './environments/environment';
+import { bootstrapApplication } from '@angular/platform-browser';
+import { provideRouter } from '@angular/router';
+import { APP_BASE_HREF } from '@angular/common';
+import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
+import { MatGridListModule } from '@angular/material/grid-list';
+
+// Standalone components you're bootstrapping
+import { SessionInformationComponent } from './app/sessionInformation/session.information.component';
+import { GeneralProxyInformationComponent } from './app/generalProxyInformation/general.proxy.information.component';
+import { TopologyInformationsComponent } from './app/topologies/topology.information.component';
+
 import './polyfills.ts';
 
-import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
-import {enableProdMode} from '@angular/core';
-import {environment} from './environments/environment';
-import {AppModule} from './app/app.module';
-
 if (environment.production) {
-    enableProdMode();
+  enableProdMode();
 }
 
-platformBrowserDynamic().bootstrapModule(AppModule);
+// Bootstrap multiple standalone components
+const bootstrapComponents = [
+  SessionInformationComponent,
+  GeneralProxyInformationComponent,
+  TopologyInformationsComponent
+];
+
+bootstrapComponents.forEach(component => {
+  bootstrapApplication(component, {
+    providers: [
+      importProvidersFrom(HttpClientModule, HttpClientXsrfModule, MatGridListModule),
+      provideRouter([]),
+      {
+        provide: APP_BASE_HREF,
+        useValue: window['base-href'] || '/'
+      }
+    ]
+  }).catch(err => console.error(err));
+});
diff --git a/knox-homepage-ui/home/polyfills.ts b/knox-homepage-ui/home/polyfills.ts
index 78cba47..bb21adf 100644
--- a/knox-homepage-ui/home/polyfills.ts
+++ b/knox-homepage-ui/home/polyfills.ts
@@ -16,20 +16,4 @@
  */
 // This file includes polyfills needed by Angular 2 and is loaded before
 // the app. You can add your own extra polyfills to this file.
-import 'core-js/es6/symbol';
-import 'core-js/es6/object';
-import 'core-js/es6/function';
-import 'core-js/es6/parse-int';
-import 'core-js/es6/parse-float';
-import 'core-js/es6/number';
-import 'core-js/es6/math';
-import 'core-js/es6/string';
-import 'core-js/es6/date';
-import 'core-js/es6/array';
-import 'core-js/es6/regexp';
-import 'core-js/es6/map';
-import 'core-js/es6/set';
-import 'core-js/es6/reflect';
-
-import 'core-js/es7/reflect';
-import 'zone.js/dist/zone';
+import 'zone.js';
diff --git a/knox-homepage-ui/home/styles.css b/knox-homepage-ui/home/styles.css
index 56888df..b193dca 100644
--- a/knox-homepage-ui/home/styles.css
+++ b/knox-homepage-ui/home/styles.css
@@ -17,7 +17,7 @@
  */
 
 /* You can add global styles to this file, and also import other style files */
-
+@import 'assets/sticky-footer.css';
 .navbar-static-top {
     min-height: 110px;
 }
@@ -25,3 +25,12 @@
 .clickable {
     cursor: pointer;
 }
+
+html, body {
+  font-size: 14px;
+}
+
+a {
+  color: #337ab7;
+  text-decoration: none;
+}
\ No newline at end of file
diff --git a/knox-homepage-ui/home/tsconfig.json b/knox-homepage-ui/home/tsconfig.json
index f04e8cc..4e53c0b 100644
--- a/knox-homepage-ui/home/tsconfig.json
+++ b/knox-homepage-ui/home/tsconfig.json
@@ -8,12 +8,11 @@
       "es2017",
       "dom"
     ],
-    "mapRoot": "./",
-    "module": "es6",
-    "moduleResolution": "node",
+    "module": "ES2022",
+    "moduleResolution": "bundler",
     "outDir": "../dist/out-tsc",
     "sourceMap": true,
-    "target": "es5",
+    "target": "ES2022",
     "typeRoots": [
       "../node_modules/@types"
     ]
diff --git a/knox-homepage-ui/package.json b/knox-homepage-ui/package.json
index 778dcdd..3f40703 100644
--- a/knox-homepage-ui/package.json
+++ b/knox-homepage-ui/package.json
@@ -2,6 +2,7 @@
   "name": "ng-knox-homepage",
   "version": "1.0.0",
   "license": "Apache-2.0",
+  "type": "module",
   "scripts": {
     "start": "ng serve --verbose=true",
     "build": "ng build",
@@ -10,48 +11,42 @@
   },
   "private": true,
   "dependencies": {
-    "@angular/animations": "^13.0.1",
-    "@angular/cdk": "^13.0.1",
-    "@angular/common": "^13.0.1",
-    "@angular/compiler": "^13.0.1",
-    "@angular/core": "^13.0.1",
-    "@angular/forms": "^13.0.1",
-    "@angular/material": "^13.0.1",
-    "@angular/platform-browser": "^13.0.1",
-    "@angular/platform-browser-dynamic": "^13.0.1",
-    "@angular/router": "^13.0.1",
-    "angular2-datatable": "^0.6.0",
-    "bootstrap": "^3.4.1",
-    "core-js": "^2.6.11",
-    "jquery": "^3.5.1",
+    "@angular/animations": "^20.3.2",
+    "@angular/cdk": "^20.2.5",
+    "@angular/common": "^20.3.2",
+    "@angular/compiler": "^20.3.2",
+    "@angular/core": "^20.3.2",
+    "@angular/forms": "^20.3.2",
+    "@angular/material": "^20.2.5",
+    "@angular/platform-browser": "^20.3.2",
+    "@angular/platform-browser-dynamic": "^20.3.2",
+    "@angular/router": "^20.3.2",
+    "bootstrap": "^5.3.8",
+    "core-js": "^3.46.0",
     "js-yaml": "^3.13.1",
-    "ng2-bs3-modal": "^0.15.0",
-    "popper.js": "^1.16.1",
-    "rxjs": "^6.6.7",
-    "rxjs-compat": "^6.6.7",
-    "sweetalert2": "^11.6.5",
+    "rxjs": "^7.8.2",
+    "sweetalert2": "^11.26.3",
     "ts-helpers": "^1.1.1",
     "vkbeautify": "^0.99.3",
-    "zone.js": "~0.11.4"
+    "zone.js": "^0.15.1"
   },
   "devDependencies": {
-    "@angular-devkit/build-angular": "^13.3.11",
-    "@angular-eslint/builder": "14.1.2",
-    "@angular-eslint/eslint-plugin": "14.1.2",
-    "@angular-eslint/eslint-plugin-template": "14.1.2",
-    "@angular-eslint/schematics": "14.1.2",
-    "@angular-eslint/template-parser": "14.1.2",
-    "@angular/cli": "^14.2.9",
-    "@angular/compiler-cli": "^13.0.1",
-    "@angular/language-service": "^5.2.0",
-    "@types/jasmine": "~2.5.53",
+    "@angular-eslint/builder": "20.3.0",
+    "@angular-eslint/eslint-plugin": "20.3.0",
+    "@angular-eslint/eslint-plugin-template": "20.3.0",
+    "@angular-eslint/schematics": "20.3.0",
+    "@angular-eslint/template-parser": "20.3.0",
+    "@angular/build": "^20.3.3",
+    "@angular/cli": "^20.3.3",
+    "@angular/compiler-cli": "^20.3.2",
+    "@angular/language-service": "^20.3.2",
+    "@eslint/js": "^9.39.0",
+    "@types/jasmine": "~5.1.0",
     "@types/jasminewd2": "^2.0.8",
-    "@types/node": "16.18.11",
-    "@typescript-eslint/eslint-plugin": "5.37.0",
-    "@typescript-eslint/parser": "5.37.0",
-    "eslint": "^8.23.1",
-    "ts-node": "~3.2.0",
-    "typescript": "~4.4.4",
-    "webdriver-manager": "10.2.5"
+    "@types/node": "^24.5.2",
+    "eslint": "^9.39.0",
+    "ts-node": "~10.9.0",
+    "typescript": "~5.8.3",
+    "typescript-eslint": "^8.46.2"
   }
 }
diff --git a/knox-homepage-ui/pom.xml b/knox-homepage-ui/pom.xml
index e3308dd..5f45cbc 100644
--- a/knox-homepage-ui/pom.xml
+++ b/knox-homepage-ui/pom.xml
@@ -65,7 +65,7 @@
                             <goal>npm</goal>
                         </goals>
                         <configuration>
-                            <arguments>install --legacy-peer-deps</arguments>
+                            <arguments>install</arguments>
                         </configuration>
                     </execution>
                     <execution>
diff --git a/knox-homepage-ui/tsconfig.json b/knox-homepage-ui/tsconfig.json
new file mode 100644
index 0000000..c76b8b0
--- /dev/null
+++ b/knox-homepage-ui/tsconfig.json
@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+    "target": "es2020",
+    "module": "esnext",
+    "moduleResolution": "node",
+    "lib": ["es2020", "dom"],
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true
+  },
+  "include": ["home/**/*.ts", "home/**/*.d.ts"],
+  "exclude": ["node_modules", "dist"]
+}
diff --git a/pom.xml b/pom.xml
index 6f1699a..f7568d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -260,7 +260,7 @@
         <mina.version>2.2.4</mina.version>
         <netty.version>4.1.127.Final</netty.version>
         <nimbus-jose-jwt.version>10.0.2</nimbus-jose-jwt.version>
-        <nodejs.version>v16.10.0</nodejs.version>
+        <nodejs.version>v22.20.0</nodejs.version>
         <okhttp.version>4.12.0</okhttp.version>
         <opensaml.version>3.4.5</opensaml.version>
         <pac4j.version>4.5.6</pac4j.version>
@@ -543,6 +543,8 @@
                         <exclude>**/knox-site/**</exclude>
                         <!-- Ignore shade-generated POM -->
                         <exclude>**/dependency-reduced-pom.xml</exclude>
+                        <exclude>**/.angular/**</exclude>
+                        <exclude>**/eslint.config.js</exclude>
                     </excludes>
                 </configuration>
             </plugin>