You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2026/07/04 18:58:40 UTC

(knox) 32/46: KNOX-3234: Upgrade Admin UI Angular to 21+, bootstrap to 5+ (#1249)

This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch knox_idf
in repository https://gitbox.apache.org/repos/asf/knox.git

commit 9563c06367bf9ddc3266e76d7e0a429b5c7ba49f
Author: hanicz <ha...@users.noreply.github.com>
AuthorDate: Tue Jun 23 14:08:40 2026 +0200

    KNOX-3234: Upgrade Admin UI Angular to 21+, bootstrap to 5+ (#1249)
    
    * KNOX-3234: Upgrade Admin UI Angular to 21+, bootstrap to 5+
    
    * KNOX-3234: Fixed editor scroll, separated components into ts,html and css
    
    (cherry picked from commit aca611f1c603f77ee0a73997d4a9eee2409fd795)
---
 gateway-admin-ui/.eslintrc.json                    |  98 ---
 gateway-admin-ui/admin-ui/app/app.component.html   |  16 +-
 .../admin-ui/app/app.component.spec.ts             |  53 --
 gateway-admin-ui/admin-ui/app/app.component.ts     |  28 +-
 gateway-admin-ui/admin-ui/app/app.module.ts        |  97 ---
 .../app/descriptor/descriptor.component.css        |   0
 .../app/descriptor/descriptor.component.html       |   3 -
 .../app/descriptor/descriptor.component.spec.ts    |  41 -
 .../gateway-version/gateway-version.component.css  |  18 +
 .../gateway-version/gateway-version.component.html |   5 +
 .../gateway-version.component.ts                   |  14 +-
 gateway-admin-ui/admin-ui/app/index.ts             |  18 -
 gateway-admin-ui/admin-ui/app/main.ts              |  21 -
 .../app/{resource-detail => model}/descriptor.ts   |   2 +-
 .../admin-ui/app/{ => model}/gateway-version.ts    |   0
 .../{resource-detail => model}/provider-config.ts  |   0
 .../admin-ui/app/{resource => model}/resource.ts   |   2 -
 .../admin-ui/app/{resource => model}/service.ts    |   0
 .../servicedefinition.ts                           |   4 -
 .../session-information.ts}                        |   3 -
 .../admin-ui/app/{ => model}/topology.ts           |   0
 .../new-desc-wizard/new-desc-wizard.component.css  |  23 +
 .../new-desc-wizard/new-desc-wizard.component.html | 244 +++---
 .../new-desc-wizard.component.spec.ts              |  41 -
 .../new-desc-wizard/new-desc-wizard.component.ts   |  24 +-
 .../provider-config-selector.component.html        |  39 +-
 .../provider-config-selector.component.spec.ts     |  41 -
 .../provider-config-selector.component.ts          |  25 +-
 .../AnonymousProviderConfig.ts                     |   2 +-
 .../authentication-wizard.ts                       |   3 +-
 .../provider-config-wizard/authorization-wizard.ts |   3 +-
 .../app/provider-config-wizard/category-wizard.ts  |   2 +-
 .../provider-config-wizard/csrf-provider-config.ts |   3 +-
 .../display-binding-provider-config.ts             |   6 +-
 .../app/provider-config-wizard/ha-wizard.ts        |   2 +-
 .../hostmap-provider-wizard.ts                     |   2 +-
 .../identity-assertion-wizard.ts                   |   5 +-
 .../provider-config-wizard.component.html          | 249 +++---
 .../provider-config-wizard.component.spec.ts       |  41 -
 .../provider-config-wizard.component.ts            |  24 +-
 .../provider-contributor-wizard.ts                 |   2 +-
 .../webappsec-provider-config.ts                   |   2 +-
 .../app/provider-config-wizard/webappsec-wizard.ts |   5 +-
 .../resource-detail/resource-detail.component.css  |  14 +-
 .../resource-detail/resource-detail.component.html | 956 +++++++++++----------
 .../resource-detail.component.spec.ts              |  41 -
 .../resource-detail/resource-detail.component.ts   |  79 +-
 .../admin-ui/app/resource/resource.component.css   |  58 ++
 .../admin-ui/app/resource/resource.component.html  |  88 +-
 .../app/resource/resource.component.spec.ts        |  41 -
 .../admin-ui/app/resource/resource.component.ts    |  52 +-
 .../app/resourcetypes/resourcetypes.component.css  |  45 +
 .../app/resourcetypes/resourcetypes.component.html |  28 +-
 .../resourcetypes/resourcetypes.component.spec.ts  |  41 -
 .../app/resourcetypes/resourcetypes.component.ts   |   4 +-
 .../new-service-definition.component.css           |  24 +-
 .../new-service-definition.component.html          |  40 +-
 .../new-service-definition.component.ts            |  44 +-
 .../app/service-definition/rewrite.rules.ts        |  21 -
 .../servicedefinition-detail.component.css         |  76 ++
 .../servicedefinition-detail.component.html        |  65 ++
 .../servicedefinition-detail.component.ts          |  93 +-
 .../app/{ => service}/gateway-version.service.ts   |  19 +-
 .../app/{resource => service}/resource.service.ts  | 100 +--
 .../resourcetypes.service.ts                       |   4 +-
 .../servicedefinition.service.ts                   |  95 +-
 .../session.information.service.ts                 |  19 +-
 .../admin-ui/app/{ => service}/topology.service.ts |  68 +-
 .../session.information.component.html             |   0
 .../session.information.component.ts               |  11 +-
 .../admin-ui/app/topology-detail.component.ts      | 209 -----
 .../topology-detail/topology-detail.component.css  |  80 ++
 .../topology-detail/topology-detail.component.html |  77 ++
 .../topology-detail/topology-detail.component.ts   | 118 +++
 .../admin-ui/app/topology/topology.component.html  |  19 +
 .../app/{ => topology}/topology.component.ts       |  30 +-
 .../admin-ui/app/utils/json-pretty.pipe.ts         |   4 +-
 .../admin-ui/app/utils/modal.component.html        |  10 +
 .../modal.component.ts}                            |  27 +-
 .../admin-ui/app/utils/tab.component.css           |   3 +
 .../admin-ui/app/utils/tab.component.html          |   2 +
 .../admin-ui/app/utils/tab.component.ts            |  11 +-
 .../admin-ui/app/utils/tabs.component.html         |   7 +
 .../admin-ui/app/utils/tabs.component.ts           |  15 +-
 .../admin-ui/app/utils/validation-utils.ts         |   2 +-
 gateway-admin-ui/admin-ui/app/utils/xml.pipe.ts    |   4 +-
 gateway-admin-ui/admin-ui/assets/sticky-footer.css |  24 -
 gateway-admin-ui/admin-ui/index.html               |  50 +-
 gateway-admin-ui/admin-ui/main.ts                  |  28 +-
 .../rewrite.rule.ts => material-theme.scss}        |  11 +-
 gateway-admin-ui/admin-ui/polyfills.ts             |  20 +-
 gateway-admin-ui/admin-ui/styles.css               |   9 -
 gateway-admin-ui/admin-ui/styles.scss              | 354 ++++++++
 gateway-admin-ui/admin-ui/tsconfig.json            |  11 +-
 gateway-admin-ui/admin-ui/typings.d.ts             |  18 -
 gateway-admin-ui/angular.json                      |  45 +-
 gateway-admin-ui/eslint.config.js                  |  76 ++
 gateway-admin-ui/package.json                      |  75 +-
 gateway-admin-ui/pom.xml                           |   8 +-
 .../session.information.component.html             |   1 -
 .../session.information.component.html             |   1 -
 101 files changed, 2344 insertions(+), 2342 deletions(-)

diff --git a/gateway-admin-ui/.eslintrc.json b/gateway-admin-ui/.eslintrc.json
deleted file mode 100644
index 71378a09f..000000000
--- a/gateway-admin-ui/.eslintrc.json
+++ /dev/null
@@ -1,98 +0,0 @@
-{
-  "root": true,
-  "overrides": [
-    {
-      "files": [
-        "*.ts"
-      ],
-      "parserOptions": {
-        "project": [
-          "admin-ui/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/gateway-admin-ui/admin-ui/app/app.component.html b/gateway-admin-ui/admin-ui/app/app.component.html
index f40042c11..b96560ee9 100644
--- a/gateway-admin-ui/admin-ui/app/app.component.html
+++ b/gateway-admin-ui/admin-ui/app/app.component.html
@@ -1,3 +1,13 @@
-<h1>
-    {{title}}
-</h1>
+<div class="container-fluid" style="padding-top: 15px;">
+    <div class="row">
+        <div class="col-md-2 col-lg-2">
+            <app-resourcetypes></app-resourcetypes>
+        </div>
+        <div class="col-md-3 col-lg-3">
+            <app-resource></app-resource>
+        </div>
+        <div class="col-md-7 col-lg-7">
+            <app-resource-detail></app-resource-detail>
+        </div>
+    </div>
+</div>
diff --git a/gateway-admin-ui/admin-ui/app/app.component.spec.ts b/gateway-admin-ui/admin-ui/app/app.component.spec.ts
deleted file mode 100644
index f34160586..000000000
--- a/gateway-admin-ui/admin-ui/app/app.component.spec.ts
+++ /dev/null
@@ -1,53 +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.
- */
-/* tslint:disable:no-unused-variable */
-import {AppComponent} from './app.component';
-
-import {TestBed} from '@angular/core/testing';
-
-import {By} from '@angular/platform-browser';
-
-// SPECS
-
-// Delete this
-describe('Smoke test', () => {
-    it('should run a passing test', () => {
-        expect(true).toEqual(true, 'should pass');
-    });
-});
-
-describe('AppComponent with TCB', function () {
-    beforeEach(() => {
-        TestBed.configureTestingModule({declarations: [AppComponent]});
-    });
-
-    it('should instantiate component', () => {
-        let fixture = TestBed.createComponent(AppComponent);
-        expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent');
-    });
-
-    it('should have expected <h1> text', () => {
-        let fixture = TestBed.createComponent(AppComponent);
-        fixture.detectChanges();
-
-        let h1 = fixture.debugElement.query(el => el.name === 'h1').nativeElement;  // it works
-
-        h1 = fixture.debugElement.query(By.css('h1')).nativeElement;            // preferred
-
-        expect(h1.innerText).toMatch(/angular 2 app/i, '<h1> should say something about "Angular 2 App"');
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/app.component.ts b/gateway-admin-ui/admin-ui/app/app.component.ts
index 63fd7e777..73374693f 100644
--- a/gateway-admin-ui/admin-ui/app/app.component.ts
+++ b/gateway-admin-ui/admin-ui/app/app.component.ts
@@ -15,33 +15,15 @@
  * limitations under the License.
  */
 import {Component} from '@angular/core';
-import {TopologyService} from './topology.service';
-import {ServiceDefinitionService} from './service-definition/servicedefinition.service';
-import {ResourceTypesService} from './resourcetypes/resourcetypes.service';
+import {ResourcetypesComponent} from './resourcetypes/resourcetypes.component';
+import {ResourceComponent} from './resource/resource.component';
+import {ResourceDetailComponent} from './resource-detail/resource-detail.component';
 
 @Component({
     selector: 'app-resource-management',
-    template: `
-        <div class="container-fluid">
-            <div class="row">
-                <div class="col-md-2 col-lg-2">
-                    <app-resourcetypes></app-resourcetypes>
-                </div>
-                <div class="col-md-3 col-lg-3">
-                    <app-resource></app-resource>
-                </div>
-                <div class="col-md-7 col-lg-7">
-                    <app-resource-detail></app-resource-detail>
-                </div>
-            </div>
-        </div>
-    `,
-    providers: [TopologyService, ServiceDefinitionService, ResourceTypesService]
+    templateUrl: './app.component.html',
+    imports: [ResourcetypesComponent, ResourceComponent, ResourceDetailComponent]
 })
 
 export class AppComponent {
-    constructor(private topologyService: TopologyService,
-                private serviceDefinitionService: ServiceDefinitionService,
-                private resourcetypesService: ResourceTypesService) {
-    }
 }
diff --git a/gateway-admin-ui/admin-ui/app/app.module.ts b/gateway-admin-ui/admin-ui/app/app.module.ts
deleted file mode 100644
index bc51ea9e9..000000000
--- a/gateway-admin-ui/admin-ui/app/app.module.ts
+++ /dev/null
@@ -1,97 +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 {NgModule} from '@angular/core';
-import {DataTableModule} from 'angular2-datatable';
-import {BrowserModule} from '@angular/platform-browser';
-import {HttpClientModule, HttpClientXsrfModule} from '@angular/common/http';
-import {FormsModule} from '@angular/forms';
-import {CustomFormsModule} from 'ng2-validation';
-import {APP_BASE_HREF} from '@angular/common';
-
-import {AppComponent} from './app.component';
-import {TopologyService} from './topology.service';
-import {ServiceDefinitionService} from './service-definition/servicedefinition.service';
-import {ServiceDefinitionDetailComponent} from './service-definition/servicedefinition-detail.component';
-import {NewServiceDefinitionComponent} from './service-definition/new-service-definition.component';
-import {GatewayVersionService} from './gateway-version.service';
-import {GatewayVersionComponent} from './gateway-version.component';
-import {TopologyComponent} from './topology.component';
-import {TopologyDetailComponent} from './topology-detail.component';
-import {XmlPipe} from './utils/xml.pipe';
-import {JsonPrettyPipe} from './utils/json-pretty.pipe';
-import {TabComponent} from './utils/tab.component';
-import {TabsComponent} from './utils/tabs.component';
-
-import {AceEditorModule} from 'ng2-ace-editor';
-import {BsModalModule} from 'ng2-bs3-modal';
-import {ResourcetypesComponent} from './resourcetypes/resourcetypes.component';
-import {ResourceTypesService} from './resourcetypes/resourcetypes.service';
-import {ResourceComponent} from './resource/resource.component';
-import {ResourceService} from './resource/resource.service';
-import {DescriptorComponent} from './descriptor/descriptor.component';
-import {ResourceDetailComponent} from './resource-detail/resource-detail.component';
-import {ProviderConfigSelectorComponent} from './provider-config-selector/provider-config-selector.component';
-import {NewDescWizardComponent} from './new-desc-wizard/new-desc-wizard.component';
-import {ProviderConfigWizardComponent} from './provider-config-wizard/provider-config-wizard.component';
-import {SessionInformationComponent} from './sessionInformation/session.information.component';
-import {SafeHtmlPipe} from './sessionInformation/session.information.component';
-
-@NgModule({
-    imports: [BrowserModule,
-        HttpClientModule,
-        HttpClientXsrfModule,
-        FormsModule,
-        CustomFormsModule,
-        BsModalModule,
-        AceEditorModule,
-        DataTableModule
-    ],
-    declarations: [AppComponent,
-        TopologyComponent,
-        TopologyDetailComponent,
-        ServiceDefinitionDetailComponent,
-        NewServiceDefinitionComponent,
-        GatewayVersionComponent,
-        XmlPipe,
-        JsonPrettyPipe,
-        TabsComponent,
-        TabComponent,
-        ResourcetypesComponent,
-        ResourceComponent,
-        DescriptorComponent,
-        ResourceDetailComponent,
-        ProviderConfigSelectorComponent,
-        NewDescWizardComponent,
-        ProviderConfigWizardComponent,
-        SessionInformationComponent,
-        SafeHtmlPipe
-    ],
-    providers: [TopologyService,
-        ServiceDefinitionService,
-        GatewayVersionService,
-        ResourceComponent,
-        ResourceTypesService,
-        ResourceService,
-        {provide: APP_BASE_HREF, useValue: '/'}
-    ],
-    bootstrap: [AppComponent,
-        GatewayVersionComponent,
-        SessionInformationComponent
-    ]
-})
-export class AppModule {
-}
diff --git a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.css b/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.html b/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.html
deleted file mode 100644
index c4e7fc1ef..000000000
--- a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<p>
-    descriptor works!
-</p>
diff --git a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.spec.ts b/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.spec.ts
deleted file mode 100644
index 850d7064b..000000000
--- a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {DescriptorComponent} from './descriptor.component';
-
-describe('DescriptorComponent', () => {
-    let component: DescriptorComponent;
-    let fixture: ComponentFixture<DescriptorComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [DescriptorComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(DescriptorComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.css b/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.css
new file mode 100644
index 000000000..d13b8581c
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.css
@@ -0,0 +1,18 @@
+:host {
+    display: inline;
+}
+
+.version-item {
+    font-size: 12px;
+    color: #888;
+}
+
+.version-item strong {
+    color: #555;
+    margin-right: 3px;
+}
+
+.version-divider {
+    margin: 0 8px;
+    color: #ccc;
+}
diff --git a/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.html b/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.html
new file mode 100644
index 000000000..a25005fc0
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.html
@@ -0,0 +1,5 @@
+@if (gatewayVersion) {
+    <span class="version-item"><strong>Version</strong> {{gatewayVersion.version}}</span>
+    <span class="version-divider">|</span>
+    <span class="version-item"><strong>Hash</strong> {{gatewayVersion.hash}}</span>
+}
diff --git a/gateway-admin-ui/admin-ui/app/gateway-version.component.ts b/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.ts
similarity index 76%
rename from gateway-admin-ui/admin-ui/app/gateway-version.component.ts
rename to gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.ts
index 602e889e6..ca8bb0afe 100644
--- a/gateway-admin-ui/admin-ui/app/gateway-version.component.ts
+++ b/gateway-admin-ui/admin-ui/app/gateway-version/gateway-version.component.ts
@@ -15,17 +15,14 @@
  * limitations under the License.
  */
 import {Component, OnInit} from '@angular/core';
-import {GatewayVersion} from './gateway-version';
-import {GatewayVersionService} from './gateway-version.service';
+import {GatewayVersion} from '../model/gateway-version';
+import {GatewayVersionService} from '../service/gateway-version.service';
+
 
 @Component({
     selector: 'app-gateway-version',
-    template: `
-        <div *ngIf="gatewayVersion">
-            <span class="small"><cite>Version</cite> {{this.gatewayVersion.version}}</span>
-            <span class="small"><cite>Hash</cite> {{this.gatewayVersion.hash}}</span>
-        </div>`,
-    providers: [GatewayVersionService]
+    templateUrl: './gateway-version.component.html',
+    styleUrls: ['./gateway-version.component.css']
 })
 
 export class GatewayVersionComponent implements OnInit {
@@ -42,4 +39,3 @@ export class GatewayVersionComponent implements OnInit {
         this.getVersion();
     }
 }
-
diff --git a/gateway-admin-ui/admin-ui/app/index.ts b/gateway-admin-ui/admin-ui/app/index.ts
deleted file mode 100644
index d7b550ca9..000000000
--- a/gateway-admin-ui/admin-ui/app/index.ts
+++ /dev/null
@@ -1,18 +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.
- */
-export * from './app.component';
-export * from './app.module';
diff --git a/gateway-admin-ui/admin-ui/app/main.ts b/gateway-admin-ui/admin-ui/app/main.ts
deleted file mode 100644
index 03ea8874f..000000000
--- a/gateway-admin-ui/admin-ui/app/main.ts
+++ /dev/null
@@ -1,21 +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 {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
-
-import {AppModule} from './app.module';
-
-platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/descriptor.ts b/gateway-admin-ui/admin-ui/app/model/descriptor.ts
similarity index 98%
rename from gateway-admin-ui/admin-ui/app/resource-detail/descriptor.ts
rename to gateway-admin-ui/admin-ui/app/model/descriptor.ts
index 85d90ae02..e9ee90cad 100644
--- a/gateway-admin-ui/admin-ui/app/resource-detail/descriptor.ts
+++ b/gateway-admin-ui/admin-ui/app/model/descriptor.ts
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Service} from '../resource/service';
+import {Service} from './service';
 
 export class Descriptor {
     discoveryType: string;
diff --git a/gateway-admin-ui/admin-ui/app/gateway-version.ts b/gateway-admin-ui/admin-ui/app/model/gateway-version.ts
similarity index 100%
rename from gateway-admin-ui/admin-ui/app/gateway-version.ts
rename to gateway-admin-ui/admin-ui/app/model/gateway-version.ts
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/provider-config.ts b/gateway-admin-ui/admin-ui/app/model/provider-config.ts
similarity index 100%
rename from gateway-admin-ui/admin-ui/app/resource-detail/provider-config.ts
rename to gateway-admin-ui/admin-ui/app/model/provider-config.ts
diff --git a/gateway-admin-ui/admin-ui/app/resource/resource.ts b/gateway-admin-ui/admin-ui/app/model/resource.ts
similarity index 95%
rename from gateway-admin-ui/admin-ui/app/resource/resource.ts
rename to gateway-admin-ui/admin-ui/app/model/resource.ts
index 1cdc2e8ba..b8774ce19 100644
--- a/gateway-admin-ui/admin-ui/app/resource/resource.ts
+++ b/gateway-admin-ui/admin-ui/app/model/resource.ts
@@ -18,8 +18,6 @@
 export class Resource {
     timestamp: number;
     name: string;
-    uri: string;
     href: string;
-    content: string;
     service: string;
 }
diff --git a/gateway-admin-ui/admin-ui/app/resource/service.ts b/gateway-admin-ui/admin-ui/app/model/service.ts
similarity index 100%
rename from gateway-admin-ui/admin-ui/app/resource/service.ts
rename to gateway-admin-ui/admin-ui/app/model/service.ts
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition.ts b/gateway-admin-ui/admin-ui/app/model/servicedefinition.ts
similarity index 89%
rename from gateway-admin-ui/admin-ui/app/service-definition/servicedefinition.ts
rename to gateway-admin-ui/admin-ui/app/model/servicedefinition.ts
index 8a71e6a49..456914855 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition.ts
+++ b/gateway-admin-ui/admin-ui/app/model/servicedefinition.ts
@@ -14,11 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {RewriteRules} from './rewrite.rules';
-import {Service} from './service';
-
 export class ServiceDefinition {
-    name: string;
     service: string;
     role: string;
     version: string;
diff --git a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.ts b/gateway-admin-ui/admin-ui/app/model/session-information.ts
similarity index 91%
rename from gateway-admin-ui/admin-ui/app/sessionInformation/session.information.ts
rename to gateway-admin-ui/admin-ui/app/model/session-information.ts
index 8bdefe3d7..98a8dbcfb 100644
--- a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.ts
+++ b/gateway-admin-ui/admin-ui/app/model/session-information.ts
@@ -17,8 +17,5 @@
 
 export class SessionInformation {
     user: string;
-    logoutUrl: string;
-    logoutPageUrl: string;
-    globalLgoutPageUrl: string;
     bannerText: string;
 }
diff --git a/gateway-admin-ui/admin-ui/app/topology.ts b/gateway-admin-ui/admin-ui/app/model/topology.ts
similarity index 100%
rename from gateway-admin-ui/admin-ui/app/topology.ts
rename to gateway-admin-ui/admin-ui/app/model/topology.ts
diff --git a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.css b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.css
index 0cc494ab8..1bd97a818 100644
--- a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.css
+++ b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.css
@@ -2,4 +2,27 @@ td {
     border-collapse: collapse;
     padding: 8px;
     vertical-align: top;
+}
+
+.services-table {
+    table-layout: fixed;
+    width: 100%;
+}
+
+.service-checkbox {
+    display: flex;
+    align-items: baseline;
+    gap: 6px;
+    margin: 4px 0;
+    cursor: pointer;
+    font-weight: normal;
+}
+
+.service-checkbox input[type="checkbox"] {
+    flex-shrink: 0;
+}
+
+.service-checkbox span {
+    overflow: hidden;
+    text-overflow: ellipsis;
 }
\ No newline at end of file
diff --git a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.html b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.html
index 1c9a9770a..4872dcf6e 100644
--- a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.html
+++ b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.html
@@ -1,149 +1,157 @@
-<bs-modal #newDescriptorModal xmlns="http://www.w3.org/1999/html">
-    <bs-modal-header [showDismiss]="true">
+<app-modal #newDescriptorModal size="lg">
+    <div class="modal-header">
         <label class="modal-title">Create a New Descriptor</label>
-    </bs-modal-header>
-    <bs-modal-body>
+        <button type="button" class="btn-close" (click)="newDescriptorModal.dismiss()"></button>
+    </div>
+    <div class="modal-body">
         <div class="panel-body">
 
             <div> <!-- Descriptor Name -->
                 <span>
-          <strong>Name</strong> <input type="text" [(ngModel)]="descriptorName">
+          <strong>Name <span class="required-marker">*</span></strong> <input type="text" [(ngModel)]="descriptorName">
         </span>
-                <span *ngIf="isMissingDescriptorName()" style="color: red">required</span>
-                <span *ngIf="!isMissingDescriptorName() && !isValidDescriptorName()"
-                      style="color: red">invalid</span>
-                <span *ngIf="isExistingReadOnlyTopology()"
-                      style="color: red">Cannot overwrite existing read-only topology</span>
+                @if (!isMissingDescriptorName() && !isValidDescriptorName()) {
+                    <span class="validation-error">Invalid name</span>
+                }
+                @if (isExistingReadOnlyTopology()) {
+                    <span class="validation-error">Cannot overwrite existing read-only topology</span>
+                }
             </div> <!-- Descriptor Name -->
 
             <br>
 
             <div> <!-- Provider Config reference -->
-                <span class="col-md-sm pull-left"><strong>Provider Configuration</strong>&nbsp;</span>
-                <span class="col-md-sm inline-editable" (click)="editModePC=true"
-                      *ngIf="!editModePC">{{descriptor.providerConfig}}</span>
-                <span class="col-md-sm inline-editor inlineEditForm" *ngIf="editModePC">
-            <input type="text" size="40" [(ngModel)]="descriptor.providerConfig">
-            <button class="btn btn-xs" (click)="editModePC=false;descriptor.setDirty()">
-              <span class="glyphicon glyphicon-ok"></span>
-            </button>
-            <button class="btn btn-xs" (click)="editModePC=false">
-              <span class="glyphicon glyphicon-remove"></span>
-            </button>
-          </span>
+                <span class="col-md-sm pull-left"><strong>Provider Configuration <span class="required-marker">*</span></strong>&nbsp;</span>
+                @if (!editModePC) {
+                    <span class="col-md-sm inline-editable" (click)="editModePC=true">{{descriptor.providerConfig}}</span>
+                }
+                @if (editModePC) {
+                    <span class="col-md-sm inline-editor inlineEditForm">
+                        <input type="text" size="40" [(ngModel)]="descriptor.providerConfig">
+                        <button class="btn btn-xs" (click)="editModePC=false;descriptor.setDirty()">
+                          <span class="material-icons">check</span>
+                        </button>
+                        <button class="btn btn-xs" (click)="editModePC=false">
+                          <span class="material-icons">close</span>
+                        </button>
+                    </span>
+                }
                 &nbsp;
-                <button id="chooseProviderConfig"
-                        class="btn btn-xs"
-                        (click)="chooseProviderConfigModal.open(descriptor, 'sm')"
-                        [disabled]="editModePC"
-                        type="submit"
-                        data-toggle="tooltip"
-                        title="Choose Provider Configuration">
-                    <span class="glyphicon glyphicon-edit"></span>
-                </button>
+                <span class="clickable inline-glyph btn btn-xs"
+                      (click)="chooseProviderConfigModal.open(descriptor)"
+                      [class.disabled]="editModePC"
+                      data-toggle="tooltip"
+                      title="Choose Provider Configuration">
+                    <span class="material-icons">edit</span>
+                </span>
                 <app-provider-config-selector #choosePC></app-provider-config-selector>
-                <span *ngIf="isMissingProviderConfig()" style="color: red">required</span>
-                <span *ngIf="!isMissingProviderConfig() && !isValidProviderConfig()"
-                      style="color: red">invalid</span>
+                @if (!isMissingProviderConfig() && !isValidProviderConfig()) {
+                    <span class="validation-error">Invalid provider configuration</span>
+                }
             </div> <!-- Provider Config reference -->
 
             <br>
 
             <!-- Services -->
             <div>
-        <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (this['showServices'] ? 'minus' : 'plus')"
-              (click)="toggleBoolean('showServices')"></span>
+        <span class="clickable inline-glyph material-icons"
+              (click)="toggleBoolean('showServices')">{{this['showServices'] ? 'expand_more' : 'chevron_right'}}</span>
                 <span (click)="toggleBoolean('showServices')"><strong>Services</strong></span>
             </div>
-            <div class="panel panel-default col-md-12" *ngIf="this['showServices']">
-                <table>
-                    <tr>
-                        <td *ngFor="let col of getServiceDisplayColumns()">
-                            <div *ngFor="let supportedService of col">
-                                <input type="checkbox"
-                                       [checked]="isSelected(supportedService)"
-                                       (click)="toggleServiceSelected(supportedService)">
-                                {{ supportedService }}
-                            </div>
-                        </td>
-                    </tr>
-                </table>
-            </div> <!-- Services -->
+            @if (this['showServices']) {
+                <div class="panel panel-default col-md-12">
+                    <table class="services-table">
+                        <tr>
+                            @for (col of getServiceDisplayColumns(); track $index) {
+                                <td>
+                                    @for (supportedService of col; track supportedService) {
+                                        <label class="service-checkbox">
+                                            <input type="checkbox"
+                                                   [checked]="isSelected(supportedService)"
+                                                   (click)="toggleServiceSelected(supportedService)">
+                                            <span>{{ supportedService }}</span>
+                                        </label>
+                                    }
+                                </td>
+                            }
+                        </tr>
+                    </table>
+                </div> <!-- Services -->
+            }
 
             <br>
 
             <!-- Discovery -->
             <div>
-        <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (this['showDiscovery'] ? 'minus' : 'plus')"
-              (click)="toggleBoolean('showDiscovery')"></span>
+        <span class="clickable inline-glyph material-icons"
+              (click)="toggleBoolean('showDiscovery')">{{this['showDiscovery'] ? 'expand_more' : 'chevron_right'}}</span>
                 <span (click)="toggleBoolean('showDiscovery')"><strong>Discovery</strong></span>
             </div>
-            <div class="panel panel-default col-md-12" *ngIf="this['showDiscovery']">
-                <div>
-                    <table>
-                        <tr>
-                            <td><strong>Type</strong></td>
-                            <td>
-                              <span>
-                                <select id="select" autofocus required class="md-select form-control"
-                                        [(ngModel)]="descriptor.discoveryType" (change)="descriptor.setDirty()">
-                                  <option *ngFor="let typeOption of resourceService.discoveryTypes"
-                                          class="md-option"
-                                          [value]="typeOption">{{typeOption}}</option>
-                                </select>
-                              </span>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td><strong>Address</strong></td>
-                            <td>
-                <span>
-                  <input type="text" [(ngModel)]="descriptor.discoveryAddress">
-                </span>
-                                <span *ngIf="!isValidDiscoveryAddress()"
-                                      style="color: red">invalid</span>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td><strong>Cluster</strong></td>
-                            <td>
-                <span>
-                  <input type="text" [(ngModel)]="descriptor.discoveryCluster">
-                </span>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td><strong>Username</strong></td>
-                            <td>
-                <span>
-                  <input type="text" [(ngModel)]="descriptor.discoveryUser">
-                </span>
-                            </td>
-                        </tr>
-                        <tr>
-                            <td><strong>Password Alias</strong></td>
-                            <td>
-                <span>
-                  <input type="text" [(ngModel)]="descriptor.discoveryPassAlias">
-                </span>
-                            </td>
-                        </tr>
-                    </table>
-                </div>
-            </div> <!-- Discovery -->
+            @if (this['showDiscovery']) {
+                <div class="panel panel-default col-md-12">
+                    <div>
+                        <table>
+                            <tr>
+                                <td><strong>Type</strong></td>
+                                <td>
+                                  <span>
+                                    <select id="select" autofocus required class="md-select form-control"
+                                            [(ngModel)]="descriptor.discoveryType" (change)="descriptor.setDirty()">
+                                      @for (typeOption of resourceService.discoveryTypes; track typeOption) {
+                                          <option class="md-option" [value]="typeOption">{{typeOption}}</option>
+                                      }
+                                    </select>
+                                  </span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td><strong>Address</strong></td>
+                                <td>
+                                    <span>
+                                      <input type="text" [(ngModel)]="descriptor.discoveryAddress">
+                                    </span>
+                                    @if (!isValidDiscoveryAddress()) {
+                                        <span class="validation-error">Invalid address</span>
+                                    }
+                                </td>
+                            </tr>
+                            <tr>
+                                <td><strong>Cluster</strong></td>
+                                <td>
+                                    <span>
+                                      <input type="text" [(ngModel)]="descriptor.discoveryCluster">
+                                    </span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td><strong>Username</strong></td>
+                                <td>
+                                    <span>
+                                      <input type="text" [(ngModel)]="descriptor.discoveryUser">
+                                    </span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td><strong>Password Alias</strong></td>
+                                <td>
+                                    <span>
+                                      <input type="text" [(ngModel)]="descriptor.discoveryPassAlias">
+                                    </span>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                </div> <!-- Discovery -->
+            }
 
         </div>
-    </bs-modal-body>
-    <bs-modal-footer>
-        <button type="button"
-                class="btn btn-default btn-sm"
-                data-dismiss="newDescriptorModal"
-                (click)="newDescriptorModal.dismiss()">Cancel
-        </button>
-        <button type="button"
-                class="btn btn-primary btn-sm"
+    </div>
+    <div class="modal-footer">
+        <span class="pull-left required-footnote">Required field</span>
+        <button type="button" class="btn btn-default btn-sm"
+                (click)="newDescriptorModal.dismiss()">Cancel</button>
+        <button type="button" class="btn btn-primary btn-sm"
                 [disabled]="!validate()"
-                (click)="save()">Ok
-        </button>
-    </bs-modal-footer>
-</bs-modal>
+                (click)="save()">Ok</button>
+    </div>
+</app-modal>
diff --git a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.spec.ts b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.spec.ts
deleted file mode 100644
index 5222c23a7..000000000
--- a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {NewDescWizardComponent} from './new-desc-wizard.component';
-
-describe('NewDescWizardComponent', () => {
-    let component: NewDescWizardComponent;
-    let fixture: ComponentFixture<NewDescWizardComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [NewDescWizardComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(NewDescWizardComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.ts b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.ts
index 4794b26a3..022122ea1 100644
--- a/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.ts
+++ b/gateway-admin-ui/admin-ui/app/new-desc-wizard/new-desc-wizard.component.ts
@@ -15,20 +15,22 @@
  * limitations under the License.
  */
 import {Component, OnInit, ViewChild} from '@angular/core';
-import {BsModalComponent} from 'ng2-bs3-modal';
+import {FormsModule} from '@angular/forms';
 
 import {ProviderConfigSelectorComponent} from '../provider-config-selector/provider-config-selector.component';
-import {Descriptor} from '../resource-detail/descriptor';
-import {ResourceService} from '../resource/resource.service';
-import {Resource} from '../resource/resource';
-import {ResourceTypesService} from '../resourcetypes/resourcetypes.service';
+import {Descriptor} from '../model/descriptor';
+import {ResourceService} from '../service/resource.service';
+import {Resource} from '../model/resource';
+import {ResourceTypesService} from '../service/resourcetypes.service';
 import {ValidationUtils} from '../utils/validation-utils';
-import {HttpErrorResponse} from '@angular/common/http';
+import {ModalComponent} from '../utils/modal.component';
+import { HttpErrorResponse } from '@angular/common/http';
 
 @Component({
     selector: 'app-new-desc-wizard',
     templateUrl: './new-desc-wizard.component.html',
-    styleUrls: ['./new-desc-wizard.component.css']
+    styleUrls: ['./new-desc-wizard.component.css'],
+    imports: [FormsModule, ProviderConfigSelectorComponent, ModalComponent]
 })
 export class NewDescWizardComponent implements OnInit {
 
@@ -80,7 +82,7 @@ export class NewDescWizardComponent implements OnInit {
         'ZEPPELINWS'];
 
     @ViewChild('newDescriptorModal')
-    childModal: BsModalComponent;
+    childModal: ModalComponent;
 
     @ViewChild('choosePC')
     chooseProviderConfigModal: ProviderConfigSelectorComponent;
@@ -102,9 +104,9 @@ export class NewDescWizardComponent implements OnInit {
         this.descriptor = new Descriptor();
     }
 
-    open(size?: string) {
+    open() {
         this.reset();
-        this.childModal.open(size ? size : 'lg');
+        this.childModal.open('lg');
     }
 
     reset() {
@@ -157,7 +159,7 @@ export class NewDescWizardComponent implements OnInit {
                         }
                     }
                 });
-                this.childModal.close(); // close the dialog if there was no error
+                this.childModal.dismiss();
             }).catch((err: HttpErrorResponse) => {
                 this.existingReadOnlyTopology = (err.status === 409);
                 console.error('Error creating ' + this.descriptorName + ' : ' + err.message);
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.html b/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.html
index 71dd9fea3..2179ac861 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.html
+++ b/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.html
@@ -1,28 +1,25 @@
-<bs-modal (onClose)="onClose()" #chooseProviderConfigModal>
-    <bs-modal-header [showDismiss]="true">
+<app-modal (onClose)="onClose()" #chooseProviderConfigModal>
+    <div class="modal-header">
         <label class="modal-title">Choose a Provider Configuration</label>
-    </bs-modal-header>
-    <bs-modal-body>
+        <button type="button" class="btn-close" (click)="chooseProviderConfigModal.dismiss()"></button>
+    </div>
+    <div class="modal-body">
         <div class="form-group">
             <select autofocus required class="md-select form-control" [(ngModel)]="selectedName"
                     id="select">
-                <option *ngFor="let pc of getProviderConfigs()"
-                        class="md-option"
-                        [value]="getReferenceName(pc.name)"
-                        [selected]="getReferenceName(pc.name) === selectedName">{{getReferenceName(pc.name)}}</option>
+                @for (pc of getProviderConfigs(); track pc.name) {
+                    <option class="md-option"
+                            [value]="getReferenceName(pc.name)"
+                            [selected]="getReferenceName(pc.name) === selectedName">{{getReferenceName(pc.name)}}</option>
+                }
             </select>
         </div>
-    </bs-modal-body>
-    <bs-modal-footer>
-        <button type="button"
-                class="btn btn-default btn-sm"
-                data-dismiss="chooseProviderConfigModal"
-                (click)="chooseProviderConfigModal.dismiss()">Cancel
-        </button>
-        <button type="button"
-                class="btn btn-primary btn-sm"
+    </div>
+    <div class="modal-footer">
+        <button type="button" class="btn btn-default btn-sm"
+                (click)="chooseProviderConfigModal.dismiss()">Cancel</button>
+        <button type="button" class="btn btn-primary btn-sm"
                 [disabled]="!selectedName"
-                (click)="chooseProviderConfigModal.close()">Ok
-        </button>
-    </bs-modal-footer>
-</bs-modal>
+                (click)="chooseProviderConfigModal.close()">Ok</button>
+    </div>
+</app-modal>
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.spec.ts b/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.spec.ts
deleted file mode 100644
index 1ea1a4648..000000000
--- a/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {ProviderConfigSelectorComponent} from './provider-config-selector.component';
-
-describe('ProviderConfigSelectorComponent', () => {
-    let component: ProviderConfigSelectorComponent;
-    let fixture: ComponentFixture<ProviderConfigSelectorComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [ProviderConfigSelectorComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(ProviderConfigSelectorComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.ts b/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.ts
index ab5a3f016..67894f382 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-selector/provider-config-selector.component.ts
@@ -14,23 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit, ViewChild} from '@angular/core';
-import {ResourceService} from '../resource/resource.service';
-import {Resource} from '../resource/resource';
-import {BsModalComponent} from 'ng2-bs3-modal';
-import {Descriptor} from '../resource-detail/descriptor';
-import {HttpErrorResponse} from '@angular/common/http';
+import {Component, ViewChild} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {ResourceService} from '../service/resource.service';
+import {Resource} from '../model/resource';
+import {Descriptor} from '../model/descriptor';
+import {ModalComponent} from '../utils/modal.component';
+import { HttpErrorResponse } from '@angular/common/http';
 
 
 @Component({
     selector: 'app-provider-config-selector',
     templateUrl: './provider-config-selector.component.html',
-    styleUrls: ['./provider-config-selector.component.css']
+    styleUrls: ['./provider-config-selector.component.css'],
+    imports: [FormsModule, ModalComponent]
 })
 export class ProviderConfigSelectorComponent {
 
     @ViewChild('chooseProviderConfigModal')
-    private childModal: BsModalComponent;
+    private childModal: ModalComponent;
 
     private providerConfigs: Resource[];
 
@@ -42,16 +44,15 @@ export class ProviderConfigSelectorComponent {
     constructor(private resourceService: ResourceService) {
     }
 
-    open(desc: Descriptor, size?: string) {
+    open(desc: Descriptor) {
         this.descriptor = desc;
-        this.selectedName = desc.providerConfig; // Set the default selection based on the current ref in the descriptor
+        this.selectedName = desc.providerConfig;
 
-        // Load the available provider configs every time this modal is open
         this.resourceService.getResources('Provider Configurations')
             .then(result => this.providerConfigs = result)
             .catch((err: HttpErrorResponse) => console.debug('Error access provider configurations: ' + err));
 
-        this.childModal.open(size);
+        this.childModal.open('sm');
     }
 
     onClose() {
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/AnonymousProviderConfig.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/AnonymousProviderConfig.ts
index b34ec60e5..c0cc1a242 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/AnonymousProviderConfig.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/AnonymousProviderConfig.ts
@@ -30,7 +30,7 @@ export class AnonymousProviderConfig extends AuthenticationProviderConfig {
         return this.displayBindingPropertyNames;
     }
 
-    getDisplayNamePropertyBinding(name: string): string {
+    getDisplayNamePropertyBinding(_name: string): string {
         return null;
     }
 
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/authentication-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/authentication-wizard.ts
index 3b1ee4da5..1ccf0f8d2 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/authentication-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/authentication-wizard.ts
@@ -82,8 +82,7 @@ export class AuthenticationWizard extends CategoryWizard {
     onChange() {
         let configType = AuthenticationWizard.typeConfigMap.get(this.selectedType);
         if (configType) {
-            this.providerConfig = Object.create(configType.prototype) as AuthenticationProviderConfig;
-            this.providerConfig = this.providerConfig.constructor.apply(this.providerConfig);
+            this.providerConfig = new (configType as any)();
             (this.providerConfig as AuthenticationProviderConfig).setType(this.selectedType);
         } else {
             console.debug('AuthenticationWizard --> No provider configuration type mapped for ' + this.selectedType);
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/authorization-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/authorization-wizard.ts
index 5d64810d3..845d4f888 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/authorization-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/authorization-wizard.ts
@@ -42,8 +42,7 @@ export class AuthorizationWizard extends CategoryWizard {
     onChange() {
         let configType = AuthorizationWizard.typeConfigMap.get(this.selectedType);
         if (configType) {
-            this.providerConfig = Object.create(configType.prototype) as DisplayBindingProviderConfig;
-            this.providerConfig = this.providerConfig.constructor.apply(this.providerConfig);
+            this.providerConfig = new (configType as any)();
             (this.providerConfig as DisplayBindingProviderConfig).setType(this.selectedType);
         } else {
             console.debug('AuthorizationWizard --> No provider configuration type mapped for ' + this.selectedType);
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/category-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/category-wizard.ts
index a28e15fef..17c514289 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/category-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/category-wizard.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 
 export abstract class CategoryWizard {
 
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/csrf-provider-config.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/csrf-provider-config.ts
index 10044e216..a3e56d29a 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/csrf-provider-config.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/csrf-provider-config.ts
@@ -57,12 +57,13 @@ export class CSRFProviderConfig extends WebAppSecurityContributor {
                 case CSRFProviderConfig.CUSTOM_HEADER:
                     isValid = ValidationUtils.isValidString(value);
                     break;
-                case CSRFProviderConfig.METHODS_TO_IGNORE:
+                case CSRFProviderConfig.METHODS_TO_IGNORE: {
                     let methodList: string[] = value.split(',');
                     for (let method of methodList) {
                         isValid = isValid && ValidationUtils.isValidHTTPMethod(method.trim().toUpperCase());
                     }
                     break;
+                }
                 default:
             }
         }
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/display-binding-provider-config.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/display-binding-provider-config.ts
index 7d864cf27..4d89ae47f 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/display-binding-provider-config.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/display-binding-provider-config.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 
 export abstract class DisplayBindingProviderConfig extends ProviderConfig {
 
@@ -59,11 +59,11 @@ export abstract class DisplayBindingProviderConfig extends ProviderConfig {
         return this.params[name];
     }
 
-    isPasswordParam(name: string): boolean {
+    isPasswordParam(_name: string): boolean {
         return false;
     }
 
-    isValidParamValue(paramName: string) {
+    isValidParamValue(_paramName: string) {
         return true;
     }
 
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/ha-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/ha-wizard.ts
index 84f489b2f..7078cea8a 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/ha-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/ha-wizard.ts
@@ -16,7 +16,7 @@
  */
 
 import {CategoryWizard} from './category-wizard';
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 import {ProviderContributorWizard} from './provider-contributor-wizard';
 import {HaProviderConfig} from './ha-provider-config';
 import {DisplayBindingProviderConfig} from './display-binding-provider-config';
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/hostmap-provider-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/hostmap-provider-wizard.ts
index 8fedb3e98..1ab1cee13 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/hostmap-provider-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/hostmap-provider-wizard.ts
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 import {CategoryWizard} from './category-wizard';
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 
 export class HostMapProviderWizard extends CategoryWizard {
     private stepCount = 2;
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/identity-assertion-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/identity-assertion-wizard.ts
index 126c8a955..8c9e4007f 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/identity-assertion-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/identity-assertion-wizard.ts
@@ -16,7 +16,7 @@
  */
 
 import {CategoryWizard} from './category-wizard';
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 import {IdentityAssertionProviderConfig} from './identity-assertion-provider-config';
 import {DefaultIdAssertionProviderConfig} from './default-idassertion-provider-config';
 import {ConcatAssertionProviderConfig} from './concat-idassertion-provider-config';
@@ -59,8 +59,7 @@ export class IdentityAssertionWizard extends CategoryWizard {
     onChange() {
         let configType = IdentityAssertionWizard.typeConfigMap.get(this.selectedType);
         if (configType) {
-            this.providerConfig = Object.create(configType.prototype) as IdentityAssertionProviderConfig;
-            this.providerConfig = this.providerConfig.constructor.apply(this.providerConfig);
+            this.providerConfig = new (configType as any)();
             (this.providerConfig as IdentityAssertionProviderConfig).setType(this.selectedType);
         } else {
             console.debug('IdentityAssertionWizard --> No provider configuration type mapped for ' + this.selectedType);
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.html b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.html
index 46cd5dc21..3c93852e0 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.html
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.html
@@ -1,133 +1,150 @@
-<bs-modal #newProviderConfigModal xmlns="http://www.w3.org/1999/html">
-    <bs-modal-header [showDismiss]="true">
+<app-modal #newProviderConfigModal>
+    <div class="modal-header">
         <label class="modal-title">Create a New Provider Configuration</label>
-    </bs-modal-header>
-    <bs-modal-body>
+        <button type="button" class="btn-close" (click)="newProviderConfigModal.dismiss()"></button>
+    </div>
+    <div class="modal-body">
 
-        <div *ngIf="isRootStep()">
-            <!-- Provider Configuration Name -->
+        @if (isRootStep()) {
             <div>
-                <table>
-                    <tr>
-                        <td><strong>Name</strong></td>
-                        <td><input type="textbox"
-                                   name="name"
-                                   #field="ngModel"
-                                   [required]="true"
-                                   size="60"
-                                   [(ngModel)]="name">
-                            <span *ngIf="field.errors?.required" style="color: red">
-                &nbsp;required
-              </span>
-                            <span *ngIf="isExistingReadOnlyProvider()"
-                                  style="color: red">Cannot overwrite existing read-only topology</span>
-                            <span *ngIf="!field.errors?.required && !isValidProviderConfigName()"
-                                  style="color: red">
-                &nbsp;invalid
-              </span>
-                        </td>
-                    </tr>
-                </table>
-            </div> <!-- Provider Configuration Name -->
+                <!-- Provider Configuration Name -->
+                <div>
+                    <table>
+                        <tr>
+                            <td><strong>Name <span class="required-marker">*</span></strong></td>
+                            <td><input type="textbox"
+                                       name="name"
+                                       #field="ngModel"
+                                       [required]="true"
+                                       size="60"
+                                       [(ngModel)]="name">
+                                @if (isExistingReadOnlyProvider()) {
+                                    <span class="validation-error">Cannot overwrite existing read-only topology</span>
+                                }
+                                @if (!field.errors?.required && !isValidProviderConfigName()) {
+                                    <span class="validation-error">Invalid name</span>
+                                }
+                            </td>
+                        </tr>
+                    </table>
+                </div> <!-- Provider Configuration Name -->
 
-            <br/>
+                <br/>
 
-            <!-- Display New Providers -->
-            <div>
-                <strong>Providers</strong><br/>
-                <div *ngFor="let pDisplay of getConfiguredProviderDisplayNames()">
-                    {{ pDisplay }}
-                </div>
-            </div> <!-- Display New Providers -->
+                <!-- Display New Providers -->
+                <div>
+                    <strong>Providers</strong><br/>
+                    @for (pDisplay of getConfiguredProviderDisplayNames(); track pDisplay) {
+                        <div>{{ pDisplay }}</div>
+                    }
+                </div> <!-- Display New Providers -->
 
-            <br/>
+                <br/>
 
-            <!-- Root Step -->
-            <div>
-                <button type="button"
-                        class="btn btn-default btn-sm"
-                        data-dismiss="newProviderConfigModal"
-                        (click)="onNextStep()">Add Provider
-                </button>
-            </div> <!-- Root Step -->
-        </div>
+                <!-- Root Step -->
+                <div>
+                    <button type="button"
+                            class="btn btn-default btn-sm"
+                            (click)="onNextStep()">Add Provider
+                    </button>
+                </div> <!-- Root Step -->
+            </div>
+        }
 
         <!-- Provider Category Selection Step -->
-        <div *ngIf="isProviderCategoryStep()">
-            <strong>Choose a Provider Category</strong><br/><br/>
-            <div *ngFor="let pc of getProviderCategories()">
-                <label>
-                    <input type="radio"
-                           [name]="pc"
-                           [(ngModel)]="selectedCategory"
-                           [value]="pc">&nbsp;{{pc}}
-                </label>
-            </div>
-        </div> <!-- Provider Category Selection Step -->
+        @if (isProviderCategoryStep()) {
+            <div>
+                <strong>Choose a Provider Category</strong><br/><br/>
+                @for (pc of getProviderCategories(); track pc) {
+                    <div>
+                        <label>
+                            <input type="radio"
+                                   [name]="pc"
+                                   [(ngModel)]="selectedCategory"
+                                   [value]="pc">&nbsp;{{pc}}
+                        </label>
+                    </div>
+                }
+            </div> <!-- Provider Category Selection Step -->
+        }
 
         <!-- Provider Type Selection Step -->
-        <div *ngIf="isProviderTypeStep()">
-            <strong>Choose a {{ selectedCategory }} Provider Type</strong><br/><br/>
-            <div *ngFor="let pt of getProviderTypes()">
-                <label>
-                    <input type="radio"
-                           [name]="pt"
-                           (change)="getCategoryWizard().onChange()"
-                           [(ngModel)]="getCategoryWizard().selectedType"
-                           [value]="pt">&nbsp;{{pt}}
-                </label>
-            </div>
-        </div> <!-- Provider Type Selection Step -->
+        @if (isProviderTypeStep()) {
+            <div>
+                <strong>Choose a {{ selectedCategory }} Provider Type</strong><br/><br/>
+                @for (pt of getProviderTypes(); track pt) {
+                    <div>
+                        <label>
+                            <input type="radio"
+                                   [name]="pt"
+                                   (change)="getCategoryWizard().onChange()"
+                                   [(ngModel)]="getCategoryWizard().selectedType"
+                                   [value]="pt">&nbsp;{{pt}}
+                        </label>
+                    </div>
+                }
+            </div> <!-- Provider Type Selection Step -->
+        }
 
         <!-- Provider Type Params Step -->
-        <div *ngIf="isProviderParamsStep()">
-            <strong>Specify {{ getCategoryWizard().getSelectedType() }} Provider Parameters</strong><br/><br/>
-            <table>
-                <tr *ngFor="let pt of getProviderParams()">
-                    <td><strong>{{pt}}</strong></td>
-                    <td>
-            <span>
-              <input [type]="!isPasswordParam(pt) || this['show'+pt] ? 'textbox' : 'password'"
-                     size="60"
-                     #paramInput
-                     (change)="setProviderParamBinding(pt, paramInput.value)"
-                     [value]="getProviderParamBinding(pt)">
-            </span>
-                        <span *ngIf="isPasswordParam(pt)">
-              <input type="checkbox"
-                     class="clickable"
-                     [checked]="getPasswordDisplay(pt)"
-                     (click)="togglePasswordDisplay(pt)">show
-            </span>
-                        <span *ngIf="!isValidParamValue(pt)" style="color: red">invalid</span>
-                    </td>
-                </tr>
-            </table>
-        </div> <!-- Provider Type Params Step -->
-
+        @if (isProviderParamsStep()) {
+            <div>
+                <strong>Specify {{ getCategoryWizard().getSelectedType() }} Provider Parameters</strong><br/><br/>
+                <table>
+                    @for (pt of getProviderParams(); track pt) {
+                        <tr>
+                            <td><strong>{{pt}}</strong></td>
+                            <td>
+                                <span>
+                                  <input [type]="!isPasswordParam(pt) || this['show'+pt] ? 'textbox' : 'password'"
+                                         size="60"
+                                         #paramInput
+                                         (change)="setProviderParamBinding(pt, paramInput.value)"
+                                         [value]="getProviderParamBinding(pt)">
+                                </span>
+                                @if (isPasswordParam(pt)) {
+                                    <span>
+                                      <input type="checkbox"
+                                             class="clickable"
+                                             [checked]="getPasswordDisplay(pt)"
+                                             (click)="togglePasswordDisplay(pt)">show
+                                    </span>
+                                }
+                                @if (!isValidParamValue(pt)) {
+                                    <span class="validation-error">Invalid value</span>
+                                }
+                            </td>
+                        </tr>
+                    }
+                </table>
+            </div> <!-- Provider Type Params Step -->
+        }
 
-    </bs-modal-body>
-    <bs-modal-footer>
+    </div>
+    <div class="modal-footer">
+        <span class="pull-left required-footnote">Required field</span>
         <button type="button"
-                class="btn btn-default btn-sm pull-left"
-                data-dismiss="newProviderConfigModal"
+                class="btn btn-default btn-sm"
                 (click)="newProviderConfigModal.dismiss()">Cancel
         </button>
-        <button type="button"
-                *ngIf="!isRootStep()"
-                class="btn btn-primary btn-sm"
-                (click)="onPreviousStep()">Back
-        </button>
-        <button type="button"
-                *ngIf="isRootStep() || !hasMoreSteps()"
-                class="btn btn-primary btn-sm"
-                [disabled]="(isRootStep() && !name)"
-                (click)="isRootStep() ? save() : onFinishAdd()">Ok
-        </button>
-        <button type="button"
-                *ngIf="!isRootStep() && hasMoreSteps()"
-                class="btn btn-primary btn-sm"
-                (click)="onNextStep()">Next
-        </button>
-    </bs-modal-footer>
-</bs-modal>
+        @if (!isRootStep()) {
+            <button type="button"
+                    class="btn btn-primary btn-sm"
+                    (click)="onPreviousStep()">Back
+            </button>
+        }
+        @if (isRootStep() || !hasMoreSteps()) {
+            <button type="button"
+                    class="btn btn-primary btn-sm"
+                    [disabled]="(isRootStep() && !name)"
+                    (click)="isRootStep() ? save() : onFinishAdd()">Ok
+            </button>
+        }
+        @if (!isRootStep() && hasMoreSteps()) {
+            <button type="button"
+                    class="btn btn-primary btn-sm"
+                    (click)="onNextStep()">Next
+            </button>
+        }
+    </div>
+</app-modal>
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.spec.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.spec.ts
deleted file mode 100644
index 2897ef0f6..000000000
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {ProviderConfigWizardComponent} from './provider-config-wizard.component';
-
-describe('ProviderConfigWizardComponent', () => {
-    let component: ProviderConfigWizardComponent;
-    let fixture: ComponentFixture<ProviderConfigWizardComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [ProviderConfigWizardComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(ProviderConfigWizardComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.ts
index a4b03a7e8..4ed21c631 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-config-wizard.component.ts
@@ -16,28 +16,30 @@
  */
 
 import {Component, OnInit, ViewChild} from '@angular/core';
-import {ResourceTypesService} from '../resourcetypes/resourcetypes.service';
-import {ResourceService} from '../resource/resource.service';
-import {BsModalComponent} from 'ng2-bs3-modal';
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {FormsModule} from '@angular/forms';
+import {ResourceTypesService} from '../service/resourcetypes.service';
+import {ResourceService} from '../service/resource.service';
+import {ProviderConfig} from '../model/provider-config';
 import {AuthenticationWizard} from './authentication-wizard';
 import {CategoryWizard} from './category-wizard';
 import {AuthorizationWizard} from './authorization-wizard';
 import {IdentityAssertionWizard} from './identity-assertion-wizard';
 import {HaWizard} from './ha-wizard';
-import {Resource} from '../resource/resource';
+import {Resource} from '../model/resource';
 import {DisplayBindingProviderConfig} from './display-binding-provider-config';
 import {OrderedParamContainer} from './ordered-param-container';
 import {HostMapProviderWizard} from './hostmap-provider-wizard';
 import {ProviderContributorWizard} from './provider-contributor-wizard';
 import {WebAppSecurityWizard} from './webappsec-wizard';
 import {ValidationUtils} from '../utils/validation-utils';
-import {HttpErrorResponse} from '@angular/common/http';
+import {ModalComponent} from '../utils/modal.component';
+import { HttpErrorResponse } from '@angular/common/http';
 
 @Component({
     selector: 'app-provider-config-wizard',
     templateUrl: './provider-config-wizard.component.html',
-    styleUrls: ['./provider-config-wizard.component.css']
+    styleUrls: ['./provider-config-wizard.component.css'],
+    imports: [FormsModule, ModalComponent]
 })
 export class ProviderConfigWizardComponent implements OnInit {
     private static CATEGORY_STEP = 1;
@@ -70,7 +72,7 @@ export class ProviderConfigWizardComponent implements OnInit {
         ]);
 
     @ViewChild('newProviderConfigModal')
-    childModal: BsModalComponent;
+    childModal: ModalComponent;
 
     private step = 0;
 
@@ -105,9 +107,9 @@ export class ProviderConfigWizardComponent implements OnInit {
         this.selectedCategory = ProviderConfigWizardComponent.CATEGORY_AUTHENTICATION; // Default to authentication
     }
 
-    open(size?: string) {
+    open() {
         this.reset();
-        this.childModal.open(size ? size : 'lg');
+        this.childModal.open('lg');
     }
 
     reset() {
@@ -226,7 +228,7 @@ export class ProviderConfigWizardComponent implements OnInit {
                         }
                     }
                 });
-                this.childModal.close(); // close the dialog if there was no error
+                this.childModal.dismiss();
             }).catch((err: HttpErrorResponse) => {
                 this.existingReadOnlyProvider = (err.status === 409);
                 console.error('Error creating ' + newResource + ' : ' + err.message);
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-contributor-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-contributor-wizard.ts
index 59062bd8b..ae32cbda7 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-contributor-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/provider-contributor-wizard.ts
@@ -16,7 +16,7 @@
  */
 
 
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 
 export interface ProviderContributorWizard {
 
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-provider-config.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-provider-config.ts
index 1825034d4..cb78133d4 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-provider-config.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-provider-config.ts
@@ -33,7 +33,7 @@ export class WebAppSecurityProviderConfig extends DisplayBindingProviderConfig {
         return [];
     }
 
-    getDisplayNamePropertyBinding(name: string): string {
+    getDisplayNamePropertyBinding(_name: string): string {
         return null;
     }
 }
diff --git a/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-wizard.ts b/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-wizard.ts
index 1f097ce57..f712ec665 100644
--- a/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-wizard.ts
+++ b/gateway-admin-ui/admin-ui/app/provider-config-wizard/webappsec-wizard.ts
@@ -16,7 +16,7 @@
  */
 
 import {CategoryWizard} from './category-wizard';
-import {ProviderConfig} from '../resource-detail/provider-config';
+import {ProviderConfig} from '../model/provider-config';
 import {ProviderContributorWizard} from './provider-contributor-wizard';
 import {DisplayBindingProviderConfig} from './display-binding-provider-config';
 import {WebAppSecurityProviderConfig} from './webappsec-provider-config';
@@ -68,8 +68,7 @@ export class WebAppSecurityWizard extends CategoryWizard implements ProviderCont
     onChange() {
         let configType = WebAppSecurityWizard.typeConfigMap.get(this.selectedType);
         if (configType) {
-            this.providerConfig = Object.create(configType.prototype) as WebAppSecurityContributor;
-            this.providerConfig = this.providerConfig.constructor.apply(this.providerConfig);
+            this.providerConfig = new (configType as any)();
             (this.providerConfig as WebAppSecurityContributor).setType(this.selectedType);
         } else {
             console.debug('WebAppSecurityWizard --> No provider configuration type mapped for ' + this.selectedType);
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.css b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.css
index c1343e95c..c9380855f 100644
--- a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.css
+++ b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.css
@@ -16,10 +16,12 @@
 }
 
 .inline-editable:hover:after {
-    font-family: 'Glyphicons Halflings';
-    content: "\270f";
+    font-family: 'Material Icons';
+    content: "\e254";
+    font-size: 14px;
     position: relative;
-    left: 10px;
+    left: 6px;
+    color: #4caf50;
 }
 
 .inline-editable.editable-empty,
@@ -42,7 +44,6 @@
 .inline-editor.select {
     width: auto;
     display: inline;
-    border-radius: 5px;
 }
 
 .inline-editor.inline-editor-button-group {
@@ -63,5 +64,8 @@
 }
 
 .inline-glyph {
-    vertical-align: 2%;
+    display: inline-block;
+    vertical-align: middle;
+    line-height: 1;
 }
+
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html
index 9b1455e3c..35cf22c0d 100644
--- a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html
+++ b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.html
@@ -1,363 +1,418 @@
-<div *ngIf="resourceType && resourceType !== 'Topologies' && resourceType !== 'Service Definitions'" class="panel panel-default">
+@if (resourceType && resourceType !== 'Topologies' && resourceType !== 'Service Definitions') {
+<div class="panel panel-default" style="margin-top: 10px;">
     <div class="panel-heading">
         <h4 class="panel-title">
             {{ getTitleSubject() }} Detail
-            <span *ngIf="showEditOptions() === false" style="padding-left: 15%;" class="text-danger text-center"> Read Only (generated file) </span>
-            <span *ngIf="hasSelectedResource()" class="pull-right">{{resourceService.getResourceDisplayName(resource)}}</span>
+            @if (showEditOptions() === false) {
+                <span style="padding-left: 15%;" class="text-danger text-center"> Read Only (generated file) </span>
+            }
+            @if (hasSelectedResource()) {
+                <span class="pull-right">{{resourceService.getResourceDisplayName(resource)}}</span>
+            }
         </h4>
     </div>
 
     <!-- Provider Configuration Details -->
-    <div class="panel-body"
-         *ngIf="hasSelectedResource() && resourceType === 'Provider Configurations'">
-        <div *ngIf="referencedProviderConfigError" class="alert alert-warning alert-dismissible"
-             role="alert">
-            <button type="button" class="close" data-dismiss="alert" aria-label="Close"
-                    (click)="referencedProviderConfigError=false">
-                <span aria-hidden="true">&times;</span>
-            </button>
-            Cannot remove <strong>{{resourceService.getResourceDisplayName(resource)}}</strong>
-            because it is referenced by one or more descriptors
-        </div>
+    @if (hasSelectedResource() && resourceType === 'Provider Configurations') {
+    <div class="panel-body">
+        @if (referencedProviderConfigError) {
+            <div class="alert alert-warning alert-dismissible" role="alert">
+                <button type="button" class="close" data-dismiss="alert" aria-label="Close"
+                        (click)="referencedProviderConfigError=false">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+                Cannot remove <strong>{{resourceService.getResourceDisplayName(resource)}}</strong>
+                because it is referenced by one or more descriptors
+            </div>
+        }
         <div style="padding-bottom: 10px">
             <span><strong>Providers</strong></span>
-            <span *ngIf="!isAddingProvider"
-                  class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs pull-right"
-                  (click)="isAddingProvider=true"
-                  data-toggle="tooltip" title="Add Provider"></span>
-            <span *ngIf="isAddingProvider" class="inline-editor inlineEditForm pull-right">
-        <input type="text" value="name" onClick="this.setSelectionRange(0, this.value.length)"
-               #newProviderName>
-        <input type="text" value="role" onClick="this.setSelectionRange(0, this.value.length)"
-               #newProviderRole>
-        <button class="btn btn-xs"
-                (click)="isAddingProvider=false;
-                         addProvider(newProviderName.value, newProviderRole.value)">
-          <span class="glyphicon glyphicon-ok "></span>
-        </button>
-        <button class="btn btn-xs" (click)="isAddingProvider=false">
-          <span class="glyphicon glyphicon-remove"></span>
-        </button>
-      </span>
-        </div>
-        <div class="panel panel-default" *ngFor="let provider of providers">
-            <div class="panel-heading">
-                <span>&nbsp;</span>
-                <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (isShowProvider(provider) ? 'minus' : 'plus')"
-                      (click)="toggleShowProvider(provider)"></span>
-                <span><strong>{{ provider.role }}</strong></span>
-                <span class="clickable inline-glyph glyphicon glyphicon-remove pull-right btn btn-xs"
-                      [title]="'Remove ' + provider.role"
-                      (click)="onRemoveProvider(provider.name)"
-                      data-toggle="tooltip"></span>
-            </div>
-            <div class="panel-body" *ngIf="isShowProvider(provider)">
-                <strong>Name</strong> {{ provider.name }}<br>
-                <strong>Enabled</strong>&nbsp;<input type="checkbox"
-                                                     [checked]="isProviderEnabled(provider)"
-                                                     (click)="onProviderEnabled(provider)"><br>
-                <div>
-          <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (isShowProviderParams(provider) ? 'minus' : 'plus')"
-                (click)="toggleShowProviderParams(provider)"></span>
-                    <span><strong>Params</strong></span>
-                    <span *ngIf="!isAddingProviderParam(provider)"
-                          class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs"
-                          (click)="setAddingProviderParam(provider,true)"
-                          data-toggle="tooltip" title="Add Param"></span>
-                    <span *ngIf="isAddingProviderParam(provider)"
-                          class="inline-editor inlineEditForm">
-            <input type="text" value="name" onClick="this.setSelectionRange(0, this.value.length)"
-                   #newParamName>
-            <input type="text" value="value" onClick="this.setSelectionRange(0, this.value.length)"
-                   #newParamValue>
-            <button class="btn btn-xs"
-                    (click)="setAddingProviderParam(provider,false);
-                             addProviderParam(provider, newParamName.value, newParamValue.value);
-                             showProviderParams(provider)">
-              <span class="glyphicon glyphicon-ok "></span>
-            </button>
-            <button class="btn btn-xs" (click)="setAddingProviderParam(provider,false)">
-              <span class="glyphicon glyphicon-remove"></span>
-            </button>
-          </span>
-                    <div class="panel panel-default table-responsive"
-                         *ngIf="isShowProviderParams(provider)">
-                        <table class="table table-sm">
-                            <tr *ngFor="let paramName of getProviderParamNames(provider)">
-                                <td>
-                  <span class="clickable inline-glyph glyphicon glyphicon-remove btn btn-xs"
-                        title="Remove Param"
-                        (click)="onRemoveProviderParam(provider, paramName)"
-                        data-toggle="tooltip"></span>
-                                </td>
-                                <td><strong>{{ paramName }}</strong></td>
-                                <td>
-                  <span class="inline-editable"
-                        (click)="setProviderParamEditFlag(provider, paramName, true)"
-                        *ngIf="!getProviderParamEditFlag(provider, paramName)">{{provider.params[paramName]}}</span>
-                                    <span *ngIf="getProviderParamEditFlag(provider, paramName)"
-                                          class="inline-editor inlineEditForm">
-                    <input type="text" size="30" [(ngModel)]="provider.params[paramName]">
+            @if (!isAddingProvider) {
+                <span class="clickable inline-glyph btn btn-xs pull-right"
+                      (click)="isAddingProvider=true"
+                      data-toggle="tooltip" title="Add Provider"><span class="material-icons">add_circle</span></span>
+            }
+            @if (isAddingProvider) {
+                <span class="inline-editor inlineEditForm pull-right">
+                    <input type="text" value="name" onClick="this.setSelectionRange(0, this.value.length)"
+                           #newProviderName>
+                    <input type="text" value="role" onClick="this.setSelectionRange(0, this.value.length)"
+                           #newProviderRole>
                     <button class="btn btn-xs"
-                            (click)="setProviderParamEditFlag(provider, paramName, false);changedProviders=providers">
-                      <span class="glyphicon glyphicon-ok"></span>
+                            (click)="isAddingProvider=false;
+                                     addProvider(newProviderName.value, newProviderRole.value)">
+                      <span class="material-icons">check</span>
                     </button>
-                    <button class="btn btn-xs"
-                            (click)="setProviderParamEditFlag(provider, paramName, false)">
-                      <span class="glyphicon glyphicon-remove"></span>
+                    <button class="btn btn-xs" (click)="isAddingProvider=false">
+                      <span class="material-icons">close</span>
                     </button>
-                  </span>
-                                </td>
-                            </tr>
-                        </table>
-                    </div> <!-- Provider params -->
+                </span>
+            }
+        </div>
+        @for (provider of providers; track provider.name) {
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <span>&nbsp;</span>
+                    <span class="clickable inline-glyph material-icons"
+                          (click)="toggleShowProvider(provider)">{{isShowProvider(provider) ? 'expand_more' : 'chevron_right'}}</span>
+                    <span><strong>{{ provider.role }}</strong></span>
+                    <span class="clickable inline-glyph pull-right btn btn-xs"
+                          [title]="'Remove ' + provider.role"
+                          (click)="onRemoveProvider(provider.name)"
+                          data-toggle="tooltip"><span class="material-icons">cancel</span></span>
                 </div>
-            </div> <!-- Provider Details -->
-        </div> <!-- Provider panel -->
+                @if (isShowProvider(provider)) {
+                <div class="panel-body">
+                    <strong>Name</strong> {{ provider.name }}<br>
+                    <strong>Enabled</strong>&nbsp;<input type="checkbox"
+                                                         [checked]="isProviderEnabled(provider)"
+                                                         (click)="onProviderEnabled(provider)"><br>
+                    <div>
+                        <span class="clickable inline-glyph material-icons"
+                              (click)="toggleShowProviderParams(provider)">{{isShowProviderParams(provider) ? 'expand_more' : 'chevron_right'}}</span>
+                        <span><strong>Params</strong></span>
+                        @if (!isAddingProviderParam(provider)) {
+                            <span class="clickable inline-glyph btn btn-xs"
+                                  (click)="setAddingProviderParam(provider,true)"
+                                  data-toggle="tooltip" title="Add Param"><span class="material-icons">add_circle</span></span>
+                        }
+                        @if (isAddingProviderParam(provider)) {
+                            <span class="inline-editor inlineEditForm">
+                                <input type="text" value="name" onClick="this.setSelectionRange(0, this.value.length)"
+                                       #newParamName>
+                                <input type="text" value="value" onClick="this.setSelectionRange(0, this.value.length)"
+                                       #newParamValue>
+                                <button class="btn btn-xs"
+                                        (click)="setAddingProviderParam(provider,false);
+                                                 addProviderParam(provider, newParamName.value, newParamValue.value);
+                                                 showProviderParams(provider)">
+                                  <span class="material-icons">check</span>
+                                </button>
+                                <button class="btn btn-xs" (click)="setAddingProviderParam(provider,false)">
+                                  <span class="material-icons">close</span>
+                                </button>
+                            </span>
+                        }
+                        @if (isShowProviderParams(provider)) {
+                            <div class="panel panel-default table-responsive">
+                                <table class="table table-sm">
+                                    @for (paramName of getProviderParamNames(provider); track paramName) {
+                                        <tr>
+                                            <td>
+                                                <span class="clickable inline-glyph btn btn-xs"
+                                                      title="Remove Param"
+                                                      (click)="onRemoveProviderParam(provider, paramName)"
+                                                      data-toggle="tooltip"><span class="material-icons">cancel</span></span>
+                                            </td>
+                                            <td><strong>{{ paramName }}</strong></td>
+                                            <td>
+                                                @if (!getProviderParamEditFlag(provider, paramName)) {
+                                                    <span class="inline-editable"
+                                                          (click)="setProviderParamEditFlag(provider, paramName, true)">{{provider.params[paramName]}}</span>
+                                                }
+                                                @if (getProviderParamEditFlag(provider, paramName)) {
+                                                    <span class="inline-editor inlineEditForm">
+                                                        <input type="text" size="30" [(ngModel)]="provider.params[paramName]">
+                                                        <button class="btn btn-xs"
+                                                                (click)="setProviderParamEditFlag(provider, paramName, false);changedProviders=providers">
+                                                          <span class="material-icons">check</span>
+                                                        </button>
+                                                        <button class="btn btn-xs"
+                                                                (click)="setProviderParamEditFlag(provider, paramName, false)">
+                                                          <span class="material-icons">close</span>
+                                                        </button>
+                                                    </span>
+                                                }
+                                            </td>
+                                        </tr>
+                                    }
+                                </table>
+                            </div> <!-- Provider params -->
+                        }
+                    </div>
+                </div> <!-- Provider Details -->
+                }
+            </div> <!-- Provider panel -->
+        }
 
-        <div> <!-- Provider Configuration Modification Buttons -->
-            <button type="button"
-                    title="Remove Provider Configuration"
-                    class="btn btn-default btn-sm pull-left"
-                    (click)="deleteConfirmModal.open('md')"
-                    *ngIf="showEditOptions()"
-                    data-toggle="tooltip">
-                <span class="glyphicon glyphicon-trash"></span>
-            </button>
-            <span class="pull-right">
-        <button type="button"
-                title="Discard Changes"
-                class="btn btn-default btn-sm"
-                [disabled]="!changedProviders"
-                (click)="discardConfirmModal.open('md')"
-                *ngIf="showEditOptions()"
-                data-toggle="tooltip">
-          <span class="glyphicon glyphicon-refresh"></span>
-        </button>
-        <span>&nbsp;</span>
-        <button type="button"
-                title="Update Provider Configuration"
-                class="btn btn-default btn-sm"
-                [disabled]="!changedProviders"
-                (click)="persistChanges()"
-                *ngIf="showEditOptions()"
-                data-toggle="tooltip">
-          <span class="glyphicon glyphicon-floppy-disk"></span>
-        </button>
-      </span>
+        <div style="overflow: hidden; padding-top: 10px;"> <!-- Provider Configuration Modification Buttons -->
+            @if (showEditOptions()) {
+                <button type="button"
+                        title="Remove Provider Configuration"
+                        class="btn btn-default btn-sm pull-left"
+                        (click)="deleteConfirmModal.open('md')"
+                        data-toggle="tooltip">
+                    <span class="material-icons">delete</span>
+                </button>
+                <span class="pull-right">
+                    <button type="button"
+                            title="Discard Changes"
+                            class="btn btn-default btn-sm"
+                            [disabled]="!changedProviders"
+                            (click)="discardConfirmModal.open('md')"
+                            data-toggle="tooltip">
+                      <span class="material-icons">refresh</span>
+                    </button>
+                    <span>&nbsp;</span>
+                    <button type="button"
+                            title="Update Provider Configuration"
+                            class="btn btn-default btn-sm"
+                            [disabled]="!changedProviders"
+                            (click)="persistChanges()"
+                            data-toggle="tooltip">
+                      <span class="material-icons">save</span>
+                    </button>
+                </span>
+            }
         </div>
     </div> <!-- Provider Configuration Details -->
+    }
 
     <!-- Descriptor Details -->
-    <div class="panel-body" *ngIf="hasSelectedResource() && resourceType === 'Descriptors'">
+    @if (hasSelectedResource() && resourceType === 'Descriptors') {
+    <div class="panel-body">
         <div> <!-- Provider Config reference -->
             <div class="panel panel-default col-md-12">
         <span class="col-md-12 pull-left">
           <span class="col-md-sm"><strong>Provider Configuration</strong>&nbsp;</span>
-          <span class="col-md-sm inline-editable" (click)="editModePC=true"
-                *ngIf="!editModePC">{{descriptor.providerConfig}}</span>
-          <span class="col-md-sm inline-editor inlineEditForm" *ngIf="editModePC">
-            <input type="text" size="40" [(ngModel)]="descriptor.providerConfig">
-            <button class="btn btn-xs" (click)="editModePC=false;descriptor.setDirty()">
-              <span class="glyphicon glyphicon-ok"></span>
-            </button>
-            <button class="btn btn-xs" (click)="editModePC=false">
-              <span class="glyphicon glyphicon-remove"></span>
-            </button>
-          </span>
+          @if (!editModePC) {
+              <span class="col-md-sm inline-editable" (click)="editModePC=true">{{descriptor.providerConfig}}</span>
+          }
+          @if (editModePC) {
+              <span class="col-md-sm inline-editor inlineEditForm">
+                <input type="text" size="40" [(ngModel)]="descriptor.providerConfig">
+                <button class="btn btn-xs" (click)="editModePC=false;descriptor.setDirty()">
+                  <span class="material-icons">check</span>
+                </button>
+                <button class="btn btn-xs" (click)="editModePC=false">
+                  <span class="material-icons">close</span>
+                </button>
+              </span>
+          }
           &nbsp;
-          <button id="chooseProviderConfig"
-                  class="btn btn-xs"
-                  (click)="chooseProviderConfigModal.open(descriptor, 'sm')"
-                  [disabled]="editModePC"
-                  type="submit"
-                  data-toggle="tooltip"
-                  title="Choose Provider Configuration">
-            <span class="glyphicon glyphicon-edit"></span>
-          </button>
+          <span class="clickable inline-glyph btn btn-xs"
+                (click)="chooseProviderConfigModal.open(descriptor, 'sm')"
+                [class.disabled]="editModePC"
+                data-toggle="tooltip"
+                title="Choose Provider Configuration">
+            <span class="material-icons">edit</span>
+          </span>
         </span>
             </div>
             <app-provider-config-selector #choosePC></app-provider-config-selector>
         </div> <!-- Provider Config reference -->
 
-        <br><br>
+        <br>
 
         <div class="panel panel-default">
             <div class="panel-heading">
-        <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (isShowServices() ? 'minus' : 'plus')"
-              (click)="toggleShowServices()"></span>
+        <span class="clickable inline-glyph material-icons"
+              (click)="toggleShowServices()">{{isShowServices() ? 'expand_more' : 'chevron_right'}}</span>
                 <span><strong>Services</strong></span>
-                <span *ngIf="!isAddingService"
-                      class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs"
-                      (click)="isAddingService=true"
-                      data-toggle="tooltip" title="Add Service"></span>
-                <span *ngIf="isAddingService" class="inline-editor inlineEditForm">
-            <input type="text" value="name" onClick="this.setSelectionRange(0, this.value.length)"
-                   #newServiceName>
-            <button class="btn btn-xs"
-                    (click)="isAddingService=false;
-                             descriptor.addService(newServiceName.value);
-                             showServices()">
-              <span class="glyphicon glyphicon-ok"></span>
-            </button>
-            <button class="btn btn-xs" (click)="isAddingService=false">
-              <span class="glyphicon glyphicon-remove"></span>
-            </button>
-        </span>
-            </div>
-            <div class="panel-body table-responsive" *ngIf="isShowServices()">
-                <table class="table table-striped table-sm">
-                    <tr *ngFor="let service of descriptor.services">
-                        <td>
-                            <div>
-                <span [class]="!getServiceVersionEditFlag(service) && !service.version ? 'inline-editable' : ''"
-                      (click)="setServiceVersionEditFlag(service, true)"
-                      title="Set version"
-                      data-toggle="tooltip"><strong>{{ service.name }}</strong></span>
-                                <span *ngIf="!getServiceVersionEditFlag(service) && service.version"
-                                      (click)="setServiceVersionEditFlag(service, true)"
-                                      [class]="service.version ? 'inline-editable' : ''">({{service.version}}
-                                    )</span>
-                                <span *ngIf="getServiceVersionEditFlag(service)"
-                                      class="inline-editor inlineEditForm">
-                  <input type="text" size="40" [(ngModel)]="service.version">
-                  <button class="btn btn-xs"
-                          (click)="setServiceVersionEditFlag(service, false);descriptor.onVersionChanged(service)">
-                    <span class="glyphicon glyphicon-ok"></span>
-                  </button>
-                  <button class="btn btn-xs" (click)="setServiceVersionEditFlag(service, false)">
-                    <span class="glyphicon glyphicon-remove"></span>
-                  </button>
-                </span>
-
-                                <span class="clickable inline-glyph glyphicon glyphicon-remove btn btn-xs pull-right"
-                                      [title]="'Remove ' + service.name"
-                                      (click)="onRemoveDescriptorService(service.name)"
-                                      data-toggle="tooltip"></span>
-                            </div>
-                            <div>
-                <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (isShowServiceParams(service) ? 'minus' : 'plus')"
-                      (click)="toggleShowServiceParams(service)"></span>
-                                <span>Params</span><span>&nbsp;</span>
-                                <span *ngIf="!isAddingServiceParam(service)"
-                                      class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs"
-                                      (click)="setAddingServiceParam(service,true)"
-                                      data-toggle="tooltip"
-                                      title="Add Param"></span>
-                                <span *ngIf="isAddingServiceParam(service)"
-                                      class="inline-editor inlineEditForm">
-                    <input type="text" value="name"
-                           onClick="this.setSelectionRange(0, this.value.length)" #newParamName>
-                    <input type="text" value="value"
-                           onClick="this.setSelectionRange(0, this.value.length)" #newParamValue>
-                    <button class="btn btn-xs"
-                            (click)="setAddingServiceParam(service,false);
-                                     descriptor.addServiceParam(service, newParamName.value, newParamValue.value);
-                                     showServiceParams(service)">
-                      <span class="glyphicon glyphicon-ok"></span>
-                    </button>
-                    <button class="btn btn-xs" (click)="setAddingServiceParam(service,false)">
-                      <span class="glyphicon glyphicon-remove"></span>
-                    </button>
-                </span>
-                            </div>
-                            <div class="table-responsive" *ngIf="isShowServiceParams(service)">
-                                <table class="table table-sm"
-                                       *ngIf="descriptor.getServiceParamNames(service).length > 0">
-                                    <tr *ngFor="let paramKey of descriptor.getServiceParamNames(service)">
-                                        <td width="5%">
-                      <span class="clickable inline-glyph glyphicon glyphicon-remove btn btn-xs"
-                            title="Remove Param"
-                            (click)="onRemoveDescriptorServiceParam(service.name, paramKey)"
-                            data-toggle="tooltip"></span>
-                                        </td>
-                                        <td width="30%"><strong>{{ paramKey }}</strong></td>
-                                        <td width="65%" align="left">
-                      <span class="inline-editable"
-                            (click)="setServiceParamEditFlag(service, paramKey, true)"
-                            *ngIf="!getServiceParamEditFlag(service, paramKey)">{{descriptor.getServiceParamValue(service, paramKey)}}</span>
-                                            <span *ngIf="getServiceParamEditFlag(service, paramKey)"
-                                                  class="inline-editor inlineEditForm">
-                      <input type="text" size="40" [(ngModel)]="service.params[paramKey]">
-                      <button class="btn btn-xs"
-                              (click)="setServiceParamEditFlag(service, paramKey, false);descriptor.setDirty()">
-                        <span class="glyphicon glyphicon-ok"></span>
-                      </button>
-                      <button class="btn btn-xs"
-                              (click)="setServiceParamEditFlag(service, paramKey, false)">
-                        <span class="glyphicon glyphicon-remove"></span>
-                      </button>
-                    </span>
-                                        </td>
-                                    </tr>
-                                </table>
-                            </div>
-                            <div>
-                <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (isShowServiceURLs(service) ? 'minus' : 'plus')"
-                      (click)="toggleShowServiceURLs(service)"></span>
-                                <span>URLs</span><span>&nbsp;</span>
-                                <span *ngIf="!isAddingServiceURL(service)"
-                                      (click)="setAddingServiceURL(service,true)"
-                                      class="inline-glyph glyphicon glyphicon-plus-sign btn btn-xs"
-                                      data-toggle="tooltip" title="Add URL"></span>
-                                <span *ngIf="isAddingServiceURL(service)"
-                                      class="inline-editor inlineEditForm">
-                    <input type="text" value="url"
-                           onClick="this.setSelectionRange(0, this.value.length)" #newURL>
-                    <button class="btn btn-xs"
-                            (click)="setAddingServiceURL(service,false);
-                                     descriptor.addServiceURL(service, newURL.value);
-                                     showServiceURLs(service)">
-                      <span class="glyphicon glyphicon-ok"></span>
-                    </button>
-                    <button class="btn btn-xs" (click)="setAddingServiceURL(service,false)">
-                      <span class="glyphicon glyphicon-remove"></span>
-                    </button>
-                </span>
-                            </div>
-                            <div class="table-responsive" *ngIf="isShowServiceURLs(service)">
-                                <table class="table table-sm"
-                                       *ngIf="service.urls && service.urls.length > 0">
-                                    <tr *ngFor="let url of service.urls; let i = index; trackBy:trackByServiceURLIndex">
-                                        <td width="5%">
-                      <span class="clickable inline-glyph glyphicon glyphicon-remove btn btn-xs"
-                            title="Remove URL"
-                            (click)="onRemoveDescriptorServiceURL(service.name, url)"
-                            data-toggle="tooltip"></span>
-                                        </td>
-                                        <td width="95%">
-                      <span class="inline-editable"
-                            (click)="setServiceURLEditFlag(service, i, true)"
-                            *ngIf="!getServiceURLEditFlag(service, i)">{{url}}</span>
-                                            <span *ngIf="getServiceURLEditFlag(service, i)"
-                                                  class="inline-editor inlineEditForm">
-                        <input type="text" size="50" [(ngModel)]="service.urls[i]">
+                @if (!isAddingService) {
+                    <span class="clickable inline-glyph btn btn-xs"
+                          (click)="isAddingService=true"
+                          data-toggle="tooltip" title="Add Service"><span class="material-icons">add_circle</span></span>
+                }
+                @if (isAddingService) {
+                    <span class="inline-editor inlineEditForm">
+                        <input type="text" value="name" onClick="this.setSelectionRange(0, this.value.length)"
+                               #newServiceName>
                         <button class="btn btn-xs"
-                                (click)="setServiceURLEditFlag(service, i, false);descriptor.setDirty()">
-                          <span class="glyphicon glyphicon-ok"></span>
+                                (click)="isAddingService=false;
+                                         descriptor.addService(newServiceName.value);
+                                         showServices()">
+                          <span class="material-icons">check</span>
                         </button>
-                        <button class="btn btn-xs"
-                                (click)="setServiceURLEditFlag(service, i, false)">
-                          <span class="glyphicon glyphicon-remove"></span>
+                        <button class="btn btn-xs" (click)="isAddingService=false">
+                          <span class="material-icons">close</span>
                         </button>
-                      </span>
-                                        </td>
-                                    </tr>
-                                </table>
-                            </div>
-                        </td>
-                    </tr>
+                    </span>
+                }
+            </div>
+            @if (isShowServices()) {
+            <div class="panel-body table-responsive">
+                <table class="table table-striped table-sm">
+                    @for (service of descriptor.services; track service.name) {
+                        <tr>
+                            <td>
+                                <div>
+                                    @if (!getServiceVersionEditFlag(service) && !service.version) {
+                                        <span class="inline-editable"
+                                              (click)="setServiceVersionEditFlag(service, true)"
+                                              title="Set version"
+                                              data-toggle="tooltip"><strong>{{ service.name }}</strong></span>
+                                    } @else {
+                                        <span (click)="setServiceVersionEditFlag(service, true)"
+                                              title="Set version"
+                                              data-toggle="tooltip"><strong>{{ service.name }}</strong></span>
+                                    }
+                                    @if (!getServiceVersionEditFlag(service) && service.version) {
+                                        <span (click)="setServiceVersionEditFlag(service, true)"
+                                              [class]="service.version ? 'inline-editable' : ''">({{service.version}})</span>
+                                    }
+                                    @if (getServiceVersionEditFlag(service)) {
+                                        <span class="inline-editor inlineEditForm">
+                                          <input type="text" size="40" [(ngModel)]="service.version">
+                                          <button class="btn btn-xs"
+                                                  (click)="setServiceVersionEditFlag(service, false);descriptor.onVersionChanged(service)">
+                                            <span class="material-icons">check</span>
+                                          </button>
+                                          <button class="btn btn-xs" (click)="setServiceVersionEditFlag(service, false)">
+                                            <span class="material-icons">close</span>
+                                          </button>
+                                        </span>
+                                    }
+
+                                    <span class="clickable inline-glyph btn btn-xs pull-right"
+                                          [title]="'Remove ' + service.name"
+                                          (click)="onRemoveDescriptorService(service.name)"
+                                          data-toggle="tooltip"><span class="material-icons">cancel</span></span>
+                                </div>
+                                <div>
+                                    <span class="clickable inline-glyph material-icons"
+                                          (click)="toggleShowServiceParams(service)">{{isShowServiceParams(service) ? 'expand_more' : 'chevron_right'}}</span>
+                                    <span>Params</span><span>&nbsp;</span>
+                                    @if (!isAddingServiceParam(service)) {
+                                        <span class="clickable inline-glyph btn btn-xs"
+                                              (click)="setAddingServiceParam(service,true)"
+                                              data-toggle="tooltip"
+                                              title="Add Param"><span class="material-icons">add_circle</span></span>
+                                    }
+                                    @if (isAddingServiceParam(service)) {
+                                        <span class="inline-editor inlineEditForm">
+                                            <input type="text" value="name"
+                                                   onClick="this.setSelectionRange(0, this.value.length)" #newParamName>
+                                            <input type="text" value="value"
+                                                   onClick="this.setSelectionRange(0, this.value.length)" #newParamValue>
+                                            <button class="btn btn-xs"
+                                                    (click)="setAddingServiceParam(service,false);
+                                                             descriptor.addServiceParam(service, newParamName.value, newParamValue.value);
+                                                             showServiceParams(service)">
+                                              <span class="material-icons">check</span>
+                                            </button>
+                                            <button class="btn btn-xs" (click)="setAddingServiceParam(service,false)">
+                                              <span class="material-icons">close</span>
+                                            </button>
+                                        </span>
+                                    }
+                                </div>
+                                @if (isShowServiceParams(service)) {
+                                    <div class="table-responsive">
+                                        @if (descriptor.getServiceParamNames(service).length > 0) {
+                                            <table class="table table-sm">
+                                                @for (paramKey of descriptor.getServiceParamNames(service); track paramKey) {
+                                                    <tr>
+                                                        <td width="5%">
+                                                            <span class="clickable inline-glyph btn btn-xs"
+                                                                  title="Remove Param"
+                                                                  (click)="onRemoveDescriptorServiceParam(service.name, paramKey)"
+                                                                  data-toggle="tooltip"><span class="material-icons">cancel</span></span>
+                                                        </td>
+                                                        <td width="30%"><strong>{{ paramKey }}</strong></td>
+                                                        <td width="65%" align="left">
+                                                            @if (!getServiceParamEditFlag(service, paramKey)) {
+                                                                <span class="inline-editable"
+                                                                      (click)="setServiceParamEditFlag(service, paramKey, true)">{{descriptor.getServiceParamValue(service, paramKey)}}</span>
+                                                            }
+                                                            @if (getServiceParamEditFlag(service, paramKey)) {
+                                                                <span class="inline-editor inlineEditForm">
+                                                                  <input type="text" size="40" [(ngModel)]="service.params[paramKey]">
+                                                                  <button class="btn btn-xs"
+                                                                          (click)="setServiceParamEditFlag(service, paramKey, false);descriptor.setDirty()">
+                                                                    <span class="material-icons">check</span>
+                                                                  </button>
+                                                                  <button class="btn btn-xs"
+                                                                          (click)="setServiceParamEditFlag(service, paramKey, false)">
+                                                                    <span class="material-icons">close</span>
+                                                                  </button>
+                                                                </span>
+                                                            }
+                                                        </td>
+                                                    </tr>
+                                                }
+                                            </table>
+                                        }
+                                    </div>
+                                }
+                                <div>
+                                    <span class="clickable inline-glyph material-icons"
+                                          (click)="toggleShowServiceURLs(service)">{{isShowServiceURLs(service) ? 'expand_more' : 'chevron_right'}}</span>
+                                    <span>URLs</span><span>&nbsp;</span>
+                                    @if (!isAddingServiceURL(service)) {
+                                        <span (click)="setAddingServiceURL(service,true)"
+                                              class="clickable inline-glyph btn btn-xs"
+                                              data-toggle="tooltip" title="Add URL"><span class="material-icons">add_circle</span></span>
+                                    }
+                                    @if (isAddingServiceURL(service)) {
+                                        <span class="inline-editor inlineEditForm">
+                                            <input type="text" value="url"
+                                                   onClick="this.setSelectionRange(0, this.value.length)" #newURL>
+                                            <button class="btn btn-xs"
+                                                    (click)="setAddingServiceURL(service,false);
+                                                             descriptor.addServiceURL(service, newURL.value);
+                                                             showServiceURLs(service)">
+                                              <span class="material-icons">check</span>
+                                            </button>
+                                            <button class="btn btn-xs" (click)="setAddingServiceURL(service,false)">
+                                              <span class="material-icons">close</span>
+                                            </button>
+                                        </span>
+                                    }
+                                </div>
+                                @if (isShowServiceURLs(service)) {
+                                    <div class="table-responsive">
+                                        @if (service.urls && service.urls.length > 0) {
+                                            <table class="table table-sm">
+                                                @for (url of service.urls; track trackByServiceURLIndex($index, url); let i = $index) {
+                                                    <tr>
+                                                        <td width="5%">
+                                                            <span class="clickable inline-glyph btn btn-xs"
+                                                                  title="Remove URL"
+                                                                  (click)="onRemoveDescriptorServiceURL(service.name, url)"
+                                                                  data-toggle="tooltip"><span class="material-icons">cancel</span></span>
+                                                        </td>
+                                                        <td width="95%">
+                                                            @if (!getServiceURLEditFlag(service, i)) {
+                                                                <span class="inline-editable"
+                                                                      (click)="setServiceURLEditFlag(service, i, true)">{{url}}</span>
+                                                            }
+                                                            @if (getServiceURLEditFlag(service, i)) {
+                                                                <span class="inline-editor inlineEditForm">
+                                                                    <input type="text" size="50" [(ngModel)]="service.urls[i]">
+                                                                    <button class="btn btn-xs"
+                                                                            (click)="setServiceURLEditFlag(service, i, false);descriptor.setDirty()">
+                                                                      <span class="material-icons">check</span>
+                                                                    </button>
+                                                                    <button class="btn btn-xs"
+                                                                            (click)="setServiceURLEditFlag(service, i, false)">
+                                                                      <span class="material-icons">close</span>
+                                                                    </button>
+                                                                </span>
+                                                            }
+                                                        </td>
+                                                    </tr>
+                                                }
+                                            </table>
+                                        }
+                                    </div>
+                                }
+                            </td>
+                        </tr>
+                    }
                 </table>
             </div> <!-- Services table -->
+            }
         </div> <!-- Services panel -->
 
         <div class="panel panel-default"> <!-- Discovery panel -->
             <div class="panel-heading">
-        <span [class]="'clickable inline-glyph glyhpicon glyphicon-' + (isShowServiceDiscovery() ? 'minus' : 'plus')"
-              (click)="toggleShowServiceDiscovery()"></span>
+        <span class="clickable inline-glyph material-icons"
+              (click)="toggleShowServiceDiscovery()">{{isShowServiceDiscovery() ? 'expand_more' : 'chevron_right'}}</span>
                 <span><strong>Discovery Details</strong></span>
             </div>
-            <div class="panel-body" *ngIf="isShowServiceDiscovery()">
+            @if (isShowServiceDiscovery()) {
+            <div class="panel-body">
                 <table class="table table-sm">
                     <!-- Discovery type field -->
                     <tr>
@@ -370,10 +425,11 @@
                             <span>
                               <select id="select" autofocus required class="md-select form-control"
                                       [(ngModel)]="descriptor.discoveryType" (change)="descriptor.setDirty()">
-                                <option *ngFor="let typeOption of resourceService.discoveryTypes"
-                                        class="md-option"
-                                        [value]="typeOption"
-                                        [selected]="descriptor.discoveryType === typeOption">{{typeOption}}</option>
+                                @for (typeOption of resourceService.discoveryTypes; track typeOption) {
+                                    <option class="md-option"
+                                            [value]="typeOption"
+                                            [selected]="descriptor.discoveryType === typeOption">{{typeOption}}</option>
+                                }
                               </select>
                             </span>
                         </td>
@@ -385,21 +441,26 @@
                     (click)="editModeAddress=true">
                 <strong>Address</strong>
               </span>
-                            <span *ngIf="!editModeAddress && !descriptor.discoveryAddress">&nbsp;&nbsp;</span>
+                            @if (!editModeAddress && !descriptor.discoveryAddress) {
+                                <span>&nbsp;&nbsp;</span>
+                            }
                         </td>
                         <td>
-              <span class="inline-editable"
-                    (click)="editModeAddress=true"
-                    *ngIf="!editModeAddress">{{ descriptor.discoveryAddress }}</span>
-                            <span *ngIf="editModeAddress" class="inline-editor inlineEditForm">
-                <input type="text" size="40" [(ngModel)]="descriptor.discoveryAddress">
-                <button class="btn btn-xs" (click)="editModeAddress=false;descriptor.setDirty()">
-                  <span class="glyphicon glyphicon-ok"></span>
-                </button>
-                <button class="btn btn-xs" (click)="editModeAddress=false">
-                  <span class="glyphicon glyphicon-remove"></span>
-                </button>
-              </span>
+                            @if (!editModeAddress) {
+                                <span class="inline-editable"
+                                      (click)="editModeAddress=true">{{ descriptor.discoveryAddress }}</span>
+                            }
+                            @if (editModeAddress) {
+                                <span class="inline-editor inlineEditForm">
+                                    <input type="text" size="40" [(ngModel)]="descriptor.discoveryAddress">
+                                    <button class="btn btn-xs" (click)="editModeAddress=false;descriptor.setDirty()">
+                                      <span class="material-icons">check</span>
+                                    </button>
+                                    <button class="btn btn-xs" (click)="editModeAddress=false">
+                                      <span class="material-icons">close</span>
+                                    </button>
+                                </span>
+                            }
                         </td>
                     </tr>
                     <!-- Discovery cluster field -->
@@ -409,21 +470,26 @@
                     (click)="editModeCluster=true">
                 <strong>Cluster</strong>
               </span>
-                            <span *ngIf="!editModeCluster && !descriptor.discoveryCluster">&nbsp;&nbsp;</span>
+                            @if (!editModeCluster && !descriptor.discoveryCluster) {
+                                <span>&nbsp;&nbsp;</span>
+                            }
                         </td>
                         <td>
-              <span class="inline-editable"
-                    (click)="editModeCluster=true"
-                    *ngIf="!editModeCluster">{{ descriptor.discoveryCluster}}</span>
-                            <span *ngIf="editModeCluster" class="inline-editor inlineEditForm">
-                <input type="text" size="40" [(ngModel)]="descriptor.discoveryCluster">
-                <button class="btn btn-xs" (click)="editModeCluster=false;descriptor.setDirty()">
-                  <span class="glyphicon glyphicon-ok"></span>
-                </button>
-                <button class="btn btn-xs" (click)="editModeCluster=false">
-                  <span class="glyphicon glyphicon-remove"></span>
-                </button>
-              </span>
+                            @if (!editModeCluster) {
+                                <span class="inline-editable"
+                                      (click)="editModeCluster=true">{{ descriptor.discoveryCluster}}</span>
+                            }
+                            @if (editModeCluster) {
+                                <span class="inline-editor inlineEditForm">
+                                    <input type="text" size="40" [(ngModel)]="descriptor.discoveryCluster">
+                                    <button class="btn btn-xs" (click)="editModeCluster=false;descriptor.setDirty()">
+                                      <span class="material-icons">check</span>
+                                    </button>
+                                    <button class="btn btn-xs" (click)="editModeCluster=false">
+                                      <span class="material-icons">close</span>
+                                    </button>
+                                </span>
+                            }
                         </td>
                     </tr>
                     <!-- Discovery user field -->
@@ -433,21 +499,26 @@
                     (click)="editModeUser=true">
                   <strong>Username</strong>
               </span>
-                            <span *ngIf="!editModeUser && !descriptor.discoveryUser">&nbsp;&nbsp;</span>
+                            @if (!editModeUser && !descriptor.discoveryUser) {
+                                <span>&nbsp;&nbsp;</span>
+                            }
                         </td>
                         <td>
-              <span class="inline-editable"
-                    (click)="editModeUser=true"
-                    *ngIf="!editModeUser">{{ descriptor.discoveryUser }}</span>
-                            <span *ngIf="editModeUser" class="inline-editor inlineEditForm">
-                <input type="text" size="40" [(ngModel)]="descriptor.discoveryUser">
-                <button class="btn btn-xs" (click)="editModeUser=false;descriptor.setDirty()">
-                  <span class="glyphicon glyphicon-ok"></span>
-                </button>
-                <button class="btn btn-xs" (click)="editModeUser=false">
-                  <span class="glyphicon glyphicon-remove"></span>
-                </button>
-              </span>
+                            @if (!editModeUser) {
+                                <span class="inline-editable"
+                                      (click)="editModeUser=true">{{ descriptor.discoveryUser }}</span>
+                            }
+                            @if (editModeUser) {
+                                <span class="inline-editor inlineEditForm">
+                                    <input type="text" size="40" [(ngModel)]="descriptor.discoveryUser">
+                                    <button class="btn btn-xs" (click)="editModeUser=false;descriptor.setDirty()">
+                                      <span class="material-icons">check</span>
+                                    </button>
+                                    <button class="btn btn-xs" (click)="editModeUser=false">
+                                      <span class="material-icons">close</span>
+                                    </button>
+                                </span>
+                            }
                         </td>
                     </tr>
                     <!-- Discovery password alias field -->
@@ -457,109 +528,112 @@
                     (click)="editModeAlias=true">
                   <strong>Password Alias</strong>
               </span>
-                            <span *ngIf="!editModeAlias && !descriptor.discoveryPassAlias">&nbsp;&nbsp;</span>
+                            @if (!editModeAlias && !descriptor.discoveryPassAlias) {
+                                <span>&nbsp;&nbsp;</span>
+                            }
                         </td>
                         <td>
-              <span class="inline-editable"
-                    (click)="editModeAlias=true"
-                    *ngIf="!editModeAlias">{{ descriptor.discoveryPassAlias }}</span>
-                            <span *ngIf="editModeAlias" class="inline-editor inlineEditForm">
-                <input type="text" size="40" [(ngModel)]="descriptor.discoveryPassAlias">
-                <button class="btn btn-xs" (click)="editModeAlias=false;descriptor.setDirty()">
-                  <span class="glyphicon glyphicon-ok"></span>
-                </button>
-                <button class="btn btn-xs" (click)="editModeAlias=false">
-                  <span class="glyphicon glyphicon-remove"></span>
-                </button>
-              </span>
+                            @if (!editModeAlias) {
+                                <span class="inline-editable"
+                                      (click)="editModeAlias=true">{{ descriptor.discoveryPassAlias }}</span>
+                            }
+                            @if (editModeAlias) {
+                                <span class="inline-editor inlineEditForm">
+                                    <input type="text" size="40" [(ngModel)]="descriptor.discoveryPassAlias">
+                                    <button class="btn btn-xs" (click)="editModeAlias=false;descriptor.setDirty()">
+                                      <span class="material-icons">check</span>
+                                    </button>
+                                    <button class="btn btn-xs" (click)="editModeAlias=false">
+                                      <span class="material-icons">close</span>
+                                    </button>
+                                </span>
+                            }
                         </td>
                     </tr>
                 </table>
             </div><!-- Discovery details -->
+            }
         </div><!-- Discovery panel -->
 
-        <div> <!-- Descriptor Modification Buttons -->
-            <button type="button"
-                    title="Remove Descriptor"
-                    class="btn btn-default btn-sm pull-left"
-                    (click)="deleteConfirmModal.open('md')"
-                    data-toggle="tooltip"
-                    *ngIf="showEditOptions()">
-                <span class="glyphicon glyphicon-trash"></span>
-            </button>
-            <span class="pull-right">
-        <button type="button"
-                title="Discard Changes"
-                class="btn btn-default btn-sm"
-                [disabled]="!descriptor.isDirty()"
-                (click)="discardConfirmModal.open('md')"
-                data-toggle="tooltip"
-                *ngIf="showEditOptions()">
-          <span class="glyphicon glyphicon-refresh"></span>
-        </button>
-        <span>&nbsp;</span>
-        <button type="button"
-                title="Update Descriptor"
-                class="btn btn-default btn-sm"
-                [disabled]="!descriptor.isDirty()"
-                (click)="persistChanges()"
-                data-toggle="tooltip"
-                *ngIf="showEditOptions()">
-          <span class="glyphicon glyphicon-floppy-disk"></span>
-        </button>
-      </span>
+        <div style="overflow: hidden; padding-top: 10px;"> <!-- Descriptor Modification Buttons -->
+            @if (showEditOptions()) {
+                <button type="button"
+                        title="Remove Descriptor"
+                        class="btn btn-default btn-sm pull-left"
+                        (click)="deleteConfirmModal.open('md')"
+                        data-toggle="tooltip">
+                    <span class="material-icons">delete</span>
+                </button>
+                <span class="pull-right">
+                    <button type="button"
+                            title="Discard Changes"
+                            class="btn btn-default btn-sm"
+                            [disabled]="!descriptor.isDirty()"
+                            (click)="discardConfirmModal.open('md')"
+                            data-toggle="tooltip">
+                      <span class="material-icons">refresh</span>
+                    </button>
+                    <span>&nbsp;</span>
+                    <button type="button"
+                            title="Update Descriptor"
+                            class="btn btn-default btn-sm"
+                            [disabled]="!descriptor.isDirty()"
+                            (click)="persistChanges()"
+                            data-toggle="tooltip">
+                      <span class="material-icons">save</span>
+                    </button>
+                </span>
+            }
         </div>
     </div> <!-- Descriptor Details -->
+    }
 
-    <div> <!-- Confirmation Modal Dialogs -->
-        <bs-modal (onClose)="deleteResource()" #deleteConfirmModal>
-            <bs-modal-header [showDismiss]="true">
+    <div>
+        <app-modal (onClose)="deleteResource()" #deleteConfirmModal>
+            <div class="modal-header">
                 <h4 class="modal-title">Deleting {{getTitleSubject()}}</h4>
-            </bs-modal-header>
-            <bs-modal-body>Are you sure you want to delete
+                <button type="button" class="btn-close" (click)="deleteConfirmModal.dismiss()"></button>
+            </div>
+            <div class="modal-body">Are you sure you want to delete
                 <strong>{{resourceService.getResourceDisplayName(resource)}}</strong>?
-            </bs-modal-body>
-            <bs-modal-footer>
-                <button type="button"
-                        class="btn btn-default btn-sm"
-                        data-dismiss="deleteConfirmModal"
-                        (click)="deleteConfirmModal.dismiss()">Cancel
-                </button>
-                <button type="button"
-                        class="btn btn-primary btn-sm"
-                        (click)="deleteConfirmModal.close()">Ok
-                </button>
-            </bs-modal-footer>
-        </bs-modal>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default btn-sm"
+                        (click)="deleteConfirmModal.dismiss()">Cancel</button>
+                <button type="button" class="btn btn-primary btn-sm"
+                        (click)="deleteConfirmModal.close()">Ok</button>
+            </div>
+        </app-modal>
 
-        <bs-modal (onClose)="discardChanges()" #discardConfirmModal>
-            <bs-modal-header [showDismiss]="true">
+        <app-modal (onClose)="discardChanges()" #discardConfirmModal>
+            <div class="modal-header">
                 <h4 class="modal-title">Discard {{getTitleSubject()}} Changes</h4>
-            </bs-modal-header>
-            <bs-modal-body>Are you sure you want to discard
+                <button type="button" class="btn-close" (click)="discardConfirmModal.dismiss()"></button>
+            </div>
+            <div class="modal-body">Are you sure you want to discard
                 <strong>{{resourceService.getResourceDisplayName(resource)}}</strong> changes?
-            </bs-modal-body>
-            <bs-modal-footer>
-                <button type="button"
-                        class="btn btn-default btn-sm"
-                        data-dismiss="revertConfirmModal"
-                        (click)="discardConfirmModal.dismiss()">Cancel
-                </button>
-                <button type="button"
-                        class="btn btn-primary btn-sm"
-                        (click)="discardConfirmModal.close()">Ok
-                </button>
-            </bs-modal-footer>
-        </bs-modal>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default btn-sm"
+                        (click)="discardConfirmModal.dismiss()">Cancel</button>
+                <button type="button" class="btn btn-primary btn-sm"
+                        (click)="discardConfirmModal.close()">Ok</button>
+            </div>
+        </app-modal>
     </div>
 </div>
+}
 
 <!-- Topology Details -->
-<div *ngIf="resourceType === 'Topologies'">
+@if (resourceType === 'Topologies') {
+<div>
     <app-topology-detail></app-topology-detail>
 </div>
+}
 
 <!-- Service Definition Details -->
-<div *ngIf="resourceType === 'Service Definitions'">
+@if (resourceType === 'Service Definitions') {
+<div>
     <app-servicedefinition-detail></app-servicedefinition-detail>
-</div>
\ No newline at end of file
+</div>
+}
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.spec.ts b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.spec.ts
deleted file mode 100644
index a081aee9a..000000000
--- a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {ResourceDetailComponent} from './resource-detail.component';
-
-describe('ResourceDetailComponent', () => {
-    let component: ResourceDetailComponent;
-    let fixture: ComponentFixture<ResourceDetailComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [ResourceDetailComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(ResourceDetailComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts
index bc110064e..93b09fbdd 100644
--- a/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts
+++ b/gateway-admin-ui/admin-ui/app/resource-detail/resource-detail.component.ts
@@ -15,25 +15,27 @@
  * limitations under the License.
  */
 import {Component, OnInit, ViewChild} from '@angular/core';
-import {ResourceService} from '../resource/resource.service';
-import {Resource} from '../resource/resource';
-import {ProviderConfig} from './provider-config';
-import {Descriptor} from './descriptor';
-import {Service} from '../resource/service';
-// Commented out in scope of KNOX-2834 (see below)
-// import {parseString} from 'xml2js';
-
-import 'brace/theme/monokai';
-import 'brace/mode/xml';
+import {FormsModule} from '@angular/forms';
+import {ResourceService} from '../service/resource.service';
+import {Resource} from '../model/resource';
+import {ProviderConfig} from '../model/provider-config';
+import {Descriptor} from '../model/descriptor';
+import {Service} from '../model/service';
 
 import {ProviderConfigSelectorComponent} from '../provider-config-selector/provider-config-selector.component';
-import {ResourceTypesService} from '../resourcetypes/resourcetypes.service';
-import {HttpErrorResponse} from '@angular/common/http';
+import {ResourceTypesService} from '../service/resourcetypes.service';
+import {TopologyDetailComponent} from '../topology-detail/topology-detail.component';
+import {ServiceDefinitionDetailComponent} from '../service-definition/servicedefinition-detail.component';
+import {ModalComponent} from '../utils/modal.component';
+import { HttpErrorResponse } from '@angular/common/http';
+import * as yaml from 'js-yaml';
 
 @Component({
     selector: 'app-resource-detail',
     templateUrl: './resource-detail.component.html',
-    styleUrls: ['./resource-detail.component.css']
+    styleUrls: ['./resource-detail.component.css'],
+    imports: [FormsModule, ProviderConfigSelectorComponent,
+        TopologyDetailComponent, ServiceDefinitionDetailComponent, ModalComponent]
 })
 
 export class ResourceDetailComponent implements OnInit {
@@ -43,8 +45,6 @@ export class ResourceDetailComponent implements OnInit {
 
     private static emptyDescriptor: Descriptor = new Descriptor();
 
-    title: string;
-
     resourceType: string;
     resource: Resource;
     resourceContent: string;
@@ -119,48 +119,10 @@ export class ResourceDetailComponent implements OnInit {
                     this.providers = contentObj['providers'];
                     this.readOnlyProviderConfig = contentObj['readOnly'];
                 } else if (res.name.endsWith('yaml') || res.name.endsWith('yml')) {
-                    // Parse the YAML representation
-                    let yaml = require('js-yaml');
-                    contentObj = yaml.safeLoad(this.resourceContent);
+                    contentObj = yaml.load(this.resourceContent);
                     this.providers = contentObj['providers'];
                     this.readOnlyProviderConfig = contentObj['readOnly'];
                 }
-                /*  Commented out in scope of KNOX-2834, because documentation does not mention supporting
-                    XML shared provider files and xml2js library is not compatible with angular 14.
-
-                    else if (res.name.endsWith('xml')) {
-                    // Parse the XML representation
-
-                    parseString(this.resourceContent,
-                        (error, result) => {
-                            if (error) {
-                                console.log('Error parsing ' + res.name + ' error: ' + error);
-                            } else {
-                                // Parsing the XML is a bit less straight-forward
-                                let tempProviders = new Array<ProviderConfig>();
-                                result['gateway'].provider.forEach(entry => {
-                                    let providerConfig: ProviderConfig = new ProviderConfig();
-                                    providerConfig.role = entry.role[0];
-                                    providerConfig.name = entry.name[0];
-                                    providerConfig.enabled = entry.enabled[0];
-
-                                    // There may not be params
-                                    if (entry.param) {
-                                        let params = new Map<string, string>();
-                                        for (let i = 0; i < entry.param.length; i++) {
-                                            let param = entry.param[i];
-                                            params[param.name[0]] = param.value[0];
-                                        }
-                                        providerConfig.params = params;
-                                    }
-                                    tempProviders.push(providerConfig);
-                                });
-                                this.providers = tempProviders;
-                                this.readOnlyProviderConfig = result['gateway'].readOnly;
-                            }
-                        }
-                    );
-                } */
             } catch (e) {
                 console.error('ResourceDetailComponent --> setProviderConfigContent() --> Error parsing ' + res.name + ' content: ' + e);
                 this.providers = null; // Clear detail display
@@ -176,7 +138,6 @@ export class ResourceDetailComponent implements OnInit {
                 if (res.name.endsWith('json')) {
                     contentObj = JSON.parse(this.resourceContent);
                 } else if (res.name.endsWith('yaml') || res.name.endsWith('yml')) {
-                    let yaml = require('js-yaml');
                     contentObj = yaml.load(this.resourceContent);
                 }
                 let tempDesc = new Descriptor();
@@ -331,7 +292,7 @@ export class ResourceDetailComponent implements OnInit {
     }
 
     onRemoveProviderParam(pc: ProviderConfig, paramName: string) {
-        if (pc.params.hasOwnProperty(paramName)) {
+        if (Object.prototype.hasOwnProperty.call(pc.params, paramName)) {
             delete pc.params[paramName];
         }
         this.changedProviders = this.providers;
@@ -352,7 +313,7 @@ export class ResourceDetailComponent implements OnInit {
         for (let i = 0; i < this.descriptor.services.length; i++) {
             if (this.descriptor.services[i].name === serviceName) {
                 let service = this.descriptor.services[i];
-                if (service.params.hasOwnProperty(paramName)) {
+                if (Object.prototype.hasOwnProperty.call(service.params, paramName)) {
                     delete service.params[paramName];
                     this.descriptor.setDirty();
                     done = true;
@@ -548,7 +509,7 @@ export class ResourceDetailComponent implements OnInit {
     }
 
     // This method is required to maintain focus on descriptor service URLs when they're being edited.
-    trackByServiceURLIndex(index: any, item: any) {
+    trackByServiceURLIndex(index: any, _item: any) {
         return index;
     }
 
@@ -573,7 +534,7 @@ export class ResourceDetailComponent implements OnInit {
 
     showEditOptions(): boolean {
         if (this.resourceType === 'Descriptors' && this.descriptor.readOnly) {
-            return !Boolean(this.descriptor.readOnly);
+            return !this.descriptor.readOnly;
         }
 
         if (this.resourceType === 'Provider Configurations' && this.readOnlyProviderConfig) {
diff --git a/gateway-admin-ui/admin-ui/app/resource/resource.component.css b/gateway-admin-ui/admin-ui/app/resource/resource.component.css
index e69de29bb..135714db5 100644
--- a/gateway-admin-ui/admin-ui/app/resource/resource.component.css
+++ b/gateway-admin-ui/admin-ui/app/resource/resource.component.css
@@ -0,0 +1,58 @@
+.table-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 6px 12px;
+    background-color: #fff;
+    border-bottom: 2px solid #4caf50;
+}
+
+.table-title {
+    font-weight: 600;
+    font-size: 13px;
+    color: #333;
+}
+
+:host ::ng-deep .mat-mdc-table {
+    background-color: #fff;
+}
+
+:host ::ng-deep .mat-mdc-header-row {
+    height: 32px;
+    background-color: #fafafa;
+}
+
+:host ::ng-deep .mat-mdc-header-cell {
+    font-size: 12px;
+    font-weight: 600;
+    color: #555;
+    padding: 0 12px;
+}
+
+:host ::ng-deep .mat-mdc-row {
+    height: 30px;
+}
+
+:host ::ng-deep .mat-mdc-cell {
+    font-size: 13px;
+    padding: 0 12px;
+    border-bottom-color: #eee;
+}
+
+:host ::ng-deep tr.mat-mdc-row:nth-child(even) {
+    background-color: #f9fdf9;
+}
+
+:host ::ng-deep tr.mat-mdc-row:hover {
+    background-color: #e8f5e9;
+}
+
+:host ::ng-deep tr.mat-mdc-row.selected,
+:host ::ng-deep tr.mat-mdc-row.active {
+    background-color: #c8e6c9;
+}
+
+:host ::ng-deep .mat-mdc-paginator {
+    font-size: 12px;
+    border-top: 1px solid #eee;
+}
diff --git a/gateway-admin-ui/admin-ui/app/resource/resource.component.html b/gateway-admin-ui/admin-ui/app/resource/resource.component.html
index 4845e6039..e7ddab505 100644
--- a/gateway-admin-ui/admin-ui/app/resource/resource.component.html
+++ b/gateway-admin-ui/admin-ui/app/resource/resource.component.html
@@ -1,51 +1,47 @@
 <div>
-    <div class="table-responsive" style="width:100%; overflow: auto; overflow-y: scroll">
-        <table class="table table-hover" [mfData]="resources" #mf="mfDataTable" [mfRowsOnPage]="10">
-            <thead>
-            <tr>
-                <th>
-          <span>
-          {{ getResourceTypeSingularDisplayName(resourceType) }}
-          </span>
-                    <span class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs pull-right"
-                          *ngIf="resourceType === 'Provider Configurations'"
-                          (click)="newProviderConfigModal.open()"
-                          title="Create New Provider Configuration"
-                          data-toggle="tooltip"></span>
-                    <span class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs pull-right"
-                          (click)="isAddingService=true"
-                          *ngIf="resourceType === 'Descriptors'"
-                          (click)="newDescriptorModal.open()"
-                          title="Create New Descriptor"
-                          data-toggle="tooltip"></span>
-                    <span class="clickable inline-glyph glyphicon glyphicon-plus-sign btn btn-xs pull-right"
-                          *ngIf="resourceType === 'Service Definitions'"
-                          (click)="newServiceDefinitionModal.open()"
-                          title="Create New Service Definition"
-                          data-toggle="tooltip"></span>
-                </th>
-                <th *ngIf="resourceType === 'Topologies'">Timestamp</th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr *ngFor="let resource of mf.data"
-                [class.selected]="resource === selectedResource"
-                [class.active]="isSelectedResource(resource)"
-                (click)="onSelect(resource)"
-                class="clickable">
-                <td>{{resourceService.getResourceDisplayName(resource)}}</td>
-                <td *ngIf="resourceType === 'Topologies'">{{resource.timestamp | date:'medium'}}</td>
-            </tr>
-            </tbody>
-		    <tfoot>
-		    <tr>
-		        <td colspan="4">
-		            <mfBootstrapPaginator [rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
-		        </td>
-		    </tr>
-		    </tfoot>
-        </table>
+    <div class="table-header">
+        <span class="table-title">{{ getResourceTypeSingularDisplayName(resourceType) }}</span>
+        @if (resourceType === 'Provider Configurations') {
+            <span class="clickable inline-glyph btn btn-xs"
+                  (click)="newProviderConfigModal.open()"
+                  title="Create New Provider Configuration"
+                  data-toggle="tooltip"><span class="material-icons">add_circle</span></span>
+        }
+        @if (resourceType === 'Descriptors') {
+            <span class="clickable inline-glyph btn btn-xs"
+                  (click)="newDescriptorModal.open()"
+                  title="Create New Descriptor"
+                  data-toggle="tooltip"><span class="material-icons">add_circle</span></span>
+        }
+        @if (resourceType === 'Service Definitions') {
+            <span class="clickable inline-glyph btn btn-xs"
+                  (click)="newServiceDefinitionModal.open()"
+                  title="Create New Service Definition"
+                  data-toggle="tooltip"><span class="material-icons">add_circle</span></span>
+        }
     </div>
+
+    <table mat-table [dataSource]="dataSource" style="width: 100%;">
+        <ng-container matColumnDef="name">
+            <th mat-header-cell *matHeaderCellDef>Name</th>
+            <td mat-cell *matCellDef="let resource">{{resourceService.getResourceDisplayName(resource)}}</td>
+        </ng-container>
+
+        <ng-container matColumnDef="timestamp">
+            <th mat-header-cell *matHeaderCellDef>Timestamp</th>
+            <td mat-cell *matCellDef="let resource">{{resource.timestamp | date:'medium'}}</td>
+        </ng-container>
+
+        <tr mat-header-row *matHeaderRowDef="getDisplayedColumns()"></tr>
+        <tr mat-row *matRowDef="let row; columns: getDisplayedColumns();"
+            [class.selected]="row === selectedResource"
+            [class.active]="isSelectedResource(row)"
+            (click)="onSelect(row)"
+            class="clickable"></tr>
+    </table>
+
+    <mat-paginator [pageSizeOptions]="[5, 10, 15]" [pageSize]="10" showFirstLastButtons></mat-paginator>
+
     <div>
         <app-new-desc-wizard #newDescriptorModal></app-new-desc-wizard>
         <app-provider-config-wizard #newProviderConfigModal></app-provider-config-wizard>
diff --git a/gateway-admin-ui/admin-ui/app/resource/resource.component.spec.ts b/gateway-admin-ui/admin-ui/app/resource/resource.component.spec.ts
deleted file mode 100644
index f5f4bc59b..000000000
--- a/gateway-admin-ui/admin-ui/app/resource/resource.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {ResourceComponent} from './resource.component';
-
-describe('ResourceComponent', () => {
-    let component: ResourceComponent;
-    let fixture: ComponentFixture<ResourceComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [ResourceComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(ResourceComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/resource/resource.component.ts b/gateway-admin-ui/admin-ui/app/resource/resource.component.ts
index 6ba0b1cff..0a2383010 100644
--- a/gateway-admin-ui/admin-ui/app/resource/resource.component.ts
+++ b/gateway-admin-ui/admin-ui/app/resource/resource.component.ts
@@ -14,26 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit} from '@angular/core';
-import {ResourceTypesService} from '../resourcetypes/resourcetypes.service';
-import {ResourceService} from './resource.service';
-import {Resource} from './resource';
-import {TopologyService} from '../topology.service';
-import {Topology} from '../topology';
-import {ServiceDefinitionService} from '../service-definition/servicedefinition.service';
-import {ServiceDefinition} from '../service-definition/servicedefinition';
-import {HttpErrorResponse} from '@angular/common/http';
+import {Component, OnInit, ViewChild, AfterViewInit} from '@angular/core';
+import {DatePipe} from '@angular/common';
+import {MatTableModule, MatTableDataSource} from '@angular/material/table';
+import {MatPaginatorModule, MatPaginator} from '@angular/material/paginator';
+import {ResourceTypesService} from '../service/resourcetypes.service';
+import {ResourceService} from '../service/resource.service';
+import {Resource} from '../model/resource';
+import {TopologyService} from '../service/topology.service';
+import {Topology} from '../model/topology';
+import {ServiceDefinitionService} from '../service/servicedefinition.service';
+import {ServiceDefinition} from '../model/servicedefinition';
+import {NewDescWizardComponent} from '../new-desc-wizard/new-desc-wizard.component';
+import {ProviderConfigWizardComponent} from '../provider-config-wizard/provider-config-wizard.component';
+import {NewServiceDefinitionComponent} from '../service-definition/new-service-definition.component';
+import { HttpErrorResponse } from '@angular/common/http';
 
 @Component({
     selector: 'app-resource',
     templateUrl: './resource.component.html',
-    styleUrls: ['./resource.component.css']
+    styleUrls: ['./resource.component.css'],
+    imports: [DatePipe, MatTableModule, MatPaginatorModule,
+        NewDescWizardComponent, ProviderConfigWizardComponent, NewServiceDefinitionComponent]
 })
-export class ResourceComponent implements OnInit {
+export class ResourceComponent implements OnInit, AfterViewInit {
     resourceType: string;
-    value: any;
-    resources: Resource[];
     selectedResource: Resource;
+    dataSource = new MatTableDataSource<Resource>([]);
+
+    @ViewChild(MatPaginator) paginator: MatPaginator;
 
     constructor(private resourceTypesService: ResourceTypesService,
                 private resourceService: ResourceService,
@@ -45,15 +54,25 @@ export class ResourceComponent implements OnInit {
         this.resourceTypesService.selectedResourceType$.subscribe(resourceType => this.setResourceType(resourceType));
     }
 
+    ngAfterViewInit() {
+        this.dataSource.paginator = this.paginator;
+    }
+
+    getDisplayedColumns(): string[] {
+        if (this.resourceType === 'Topologies') {
+            return ['name', 'timestamp'];
+        }
+        return ['name'];
+    }
+
     setResourceType(resType: string) {
-        // Clear the selected resource, so it can be removed from the list on refresh if necessary
         this.selectedResource = null;
 
         this.resourceType = resType;
         this.resourceService.selectedResourceType(this.resourceType);
         this.resourceService.getResources(resType)
             .then(resources => {
-                this.resources = resources;
+                this.dataSource.data = resources;
 
                 let debugMsg = 'ResourceComponent --> Found ' + resources.length + ' ' + resType + ' resources\n';
                 for (let res of resources) {
@@ -73,7 +92,6 @@ export class ResourceComponent implements OnInit {
     onSelect(resource: Resource) {
         this.selectedResource = resource;
 
-        // If it's a topology resource, notify the topology service
         if (this.resourceType === 'Topologies') {
             let topology = new Topology();
             topology.name = resource.name;
@@ -81,13 +99,11 @@ export class ResourceComponent implements OnInit {
             this.topologyService.selectedTopology(topology);
         } else if (this.resourceType === 'Service Definitions') {
            let serviceDefinition = new ServiceDefinition();
-           serviceDefinition.name = resource.name;
            serviceDefinition.service = resource.service['name'];
            serviceDefinition.role = resource.service['role'];
            serviceDefinition.version = resource.service['version'];
            this.serviceDefinitionService.selectedServiceDefinition(serviceDefinition);
         } else {
-            // Otherwise, notify the resource service
             this.resourceService.selectedResource(resource);
         }
     }
diff --git a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.css b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.css
index e69de29bb..ba98cb320 100644
--- a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.css
+++ b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.css
@@ -0,0 +1,45 @@
+.resource-types-panel {
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    overflow: hidden;
+}
+
+.resource-types-header {
+    padding: 8px 12px;
+    font-weight: 600;
+    font-size: 13px;
+    color: #333;
+    background-color: #fff;
+    border-bottom: 2px solid #4caf50;
+}
+
+.resource-types-list {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+
+.resource-type-item {
+    padding: 8px 12px;
+    font-size: 13px;
+    border-bottom: 1px solid #eee;
+    transition: background-color 0.1s;
+}
+
+.resource-type-item:last-child {
+    border-bottom: none;
+}
+
+.resource-type-item:hover {
+    background-color: #e8f5e9;
+}
+
+.resource-type-item.selected {
+    background-color: #4caf50;
+    color: #fff;
+    font-weight: 500;
+}
+
+.resource-type-item.selected:hover {
+    background-color: #388e3c;
+}
diff --git a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.html b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.html
index e84539262..c22963ad9 100644
--- a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.html
+++ b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.html
@@ -1,18 +1,12 @@
-<div class="table-responsive" style="width:100%; overflow: auto;">
-    <table class="table table-hover">
-        <thead>
-        <tr>
-            <th>Resource Types</th>
-        </tr>
-        </thead>
-        <tbody>
-        <tr *ngFor="let type of resourceTypes"
-            [class.selected]="type === selectedResourceType"
-            [class.active]="type === selectedResourceType"
-            (click)="onSelect(type)"
-            class="clickable">
-            <td>{{type}}</td>
-        </tr>
-        </tbody>
-    </table>
+<div class="resource-types-panel">
+    <div class="resource-types-header">Resource Types</div>
+    <ul class="resource-types-list">
+        @for (type of resourceTypes; track type) {
+            <li [class.selected]="type === selectedResourceType"
+                (click)="onSelect(type)"
+                class="clickable resource-type-item">
+                {{type}}
+            </li>
+        }
+    </ul>
 </div>
diff --git a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.spec.ts b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.spec.ts
deleted file mode 100644
index 2790701ba..000000000
--- a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.spec.ts
+++ /dev/null
@@ -1,41 +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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
-
-import {ResourcetypesComponent} from './resourcetypes.component';
-
-describe('ResourcetypesComponent', () => {
-    let component: ResourcetypesComponent;
-    let fixture: ComponentFixture<ResourcetypesComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [ResourcetypesComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(ResourcetypesComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.ts b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.ts
index f278e838e..5a62a935c 100644
--- a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.ts
+++ b/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.component.ts
@@ -14,8 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, Injectable, OnInit} from '@angular/core';
-import {ResourceTypesService} from './resourcetypes.service';
+import {Component, OnInit} from '@angular/core';
+import {ResourceTypesService} from '../service/resourcetypes.service';
 
 @Component({
     selector: 'app-resourcetypes',
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.css b/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.css
index 0cc494ab8..52e0fee93 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.css
+++ b/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.css
@@ -2,4 +2,26 @@ td {
     border-collapse: collapse;
     padding: 8px;
     vertical-align: top;
-}
\ No newline at end of file
+}
+
+.code-editor-modal {
+    min-height: 430px;
+    width: 100%;
+    padding: 12px;
+    background-color: #272822;
+    color: #f8f8f2;
+    border: 1px solid #3e3d32;
+    border-radius: 4px;
+    outline: none;
+    resize: vertical;
+    font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
+    font-size: 12px;
+    line-height: 18px;
+    tab-size: 4;
+    white-space: pre;
+    overflow: auto;
+}
+
+.code-editor-modal::selection {
+    background-color: #49483e;
+}
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.html b/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.html
index 89a2716f3..a18816a76 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.html
+++ b/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.html
@@ -1,27 +1,21 @@
-<bs-modal (onClose)="onClose()" #newServiceDefinitionModal xmlns="http://www.w3.org/1999/html"> 
-    <bs-modal-header [showDismiss]="true">
+<app-modal #newServiceDefinitionModal>
+    <div class="modal-header">
         <label class="modal-title">Create a New Service Definition</label>
-    </bs-modal-header>
-    <bs-modal-body>
-        <div class="panel-body">
-            <div>
-                <span style="text-shadow: infotext; text-align: center;">At a minimum you must modify the service's name and role attributes!</span>
-            </div>
-            <br />
-            <div>
-                <ace-editor
-                        #editor
-                        [(text)]="serviceDefinitionContent"
-                        [options]="options"
-                        [theme]="theme"
-                        style="min-height: 430px; width:100%; overflow: auto;">
-                </ace-editor>
-            </div>
+        <button type="button" class="btn-close" (click)="newServiceDefinitionModal.dismiss()"></button>
+    </div>
+    <div class="modal-body" style="padding: 12px;">
+        <div class="alert alert-info" style="margin-bottom: 10px; font-size: 12px;">
+            At a minimum you must modify the service's <strong>name</strong> and <strong>role</strong> attributes.
         </div>
-    </bs-modal-body>
-    <bs-modal-footer>
+        <textarea
+                [(ngModel)]="serviceDefinitionContent"
+                spellcheck="false"
+                class="code-editor-modal">
+        </textarea>
+    </div>
+    <div class="modal-footer">
         <button type="button" class="btn btn-default btn-sm" (click)="newServiceDefinitionModal.dismiss()">Cancel</button>
-        <button type="button" class="btn btn-primary btn-sm" (click)="newServiceDefinitionModal.close()"
+        <button type="button" class="btn btn-primary btn-sm" (click)="onClose()"
                 [disabled]="!validate()">Ok</button>
-    </bs-modal-footer>
-</bs-modal>
+    </div>
+</app-modal>
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.ts b/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.ts
index cb99ccde8..912bb1f07 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.ts
+++ b/gateway-admin-ui/admin-ui/app/service-definition/new-service-definition.component.ts
@@ -14,34 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
-import {HttpClient} from '@angular/common/http';
-import {BsModalComponent} from 'ng2-bs3-modal';
-import Swal from 'sweetalert2';
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import { HttpClient } from '@angular/common/http';
+import Swal from 'sweetalert2/dist/sweetalert2.esm.all.js';
 
-import {ServiceDefinitionService} from './servicedefinition.service';
-import {ResourceTypesService} from '../resourcetypes/resourcetypes.service';
-import * as ace from 'ace-builds';
+import {ServiceDefinitionService} from '../service/servicedefinition.service';
+import {ResourceTypesService} from '../service/resourcetypes.service';
+import {ModalComponent} from '../utils/modal.component';
 
 
 @Component({
     selector: 'app-service-definition-wizard',
     templateUrl: './new-service-definition.component.html',
-    styleUrls: ['./new-service-definition.component.css']
+    styleUrls: ['./new-service-definition.component.css'],
+    imports: [FormsModule, ModalComponent]
 })
 export class NewServiceDefinitionComponent implements OnInit {
 
+    @ViewChild('newServiceDefinitionModal')
+    childModal: ModalComponent;
+
     serviceDefinitionXmlTemplate = 'assets/new-service-definition-template.xml';
     defaultServiceDefinitionContent: string;
     serviceDefinitionContent: string;
-    theme: String = 'monokai';
-    options: any = {useWorker: false, printMargin: false};
-
-    @ViewChild('newServiceDefinitionModal')
-    childModal: BsModalComponent;
-
-    @ViewChild('editor')
-    editor: ElementRef<HTMLElement>;
 
     constructor(private http: HttpClient,
                 private serviceDefinitionService: ServiceDefinitionService,
@@ -53,18 +49,9 @@ export class NewServiceDefinitionComponent implements OnInit {
                      .subscribe(data => this.defaultServiceDefinitionContent = data);
     }
 
-    ngAfterViewInit(): void {
-        ace.config.set(
-            'basePath',
-            'https://unpkg.com/ace-builds@1.4.12/src-noconflict'
-        );
-        const aceEditor = ace.edit(this.editor.nativeElement);
-        aceEditor.session.setMode('xml');
-    }
-
-    open(size?: string) {
+    open() {
         this.reset();
-        this.childModal.open(size ? size : 'lg');
+        this.childModal.open('lg');
     }
 
     reset() {
@@ -73,7 +60,8 @@ export class NewServiceDefinitionComponent implements OnInit {
 
     onClose() {
         this.serviceDefinitionService.saveNewServiceDefinition(this.serviceDefinitionContent)
-                                     .then(response => {
+                                     .then(() => {
+                                        this.childModal.dismiss();
                                         Swal.fire({
                                             text: 'Saved successfully!',
                                             confirmButtonColor: '#7cd1f9'
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/rewrite.rules.ts b/gateway-admin-ui/admin-ui/app/service-definition/rewrite.rules.ts
deleted file mode 100644
index d6629811c..000000000
--- a/gateway-admin-ui/admin-ui/app/service-definition/rewrite.rules.ts
+++ /dev/null
@@ -1,21 +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 {RewriteRule} from './rewrite.rule';
-
-export class RewriteRules {
-    rules: RewriteRule[];
-}
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.css b/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.css
new file mode 100644
index 000000000..f8e5a066b
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.css
@@ -0,0 +1,76 @@
+.code-editor-scroll {
+    height: 65vh;
+    overflow: auto;
+    background-color: #272822;
+    border-radius: 4px 4px 0 0;
+}
+
+.code-editor-wrapper {
+    display: flex;
+    align-items: stretch;
+    min-height: 100%;
+}
+
+.code-editor-gutter {
+    min-width: 40px;
+    padding: 10px 8px 10px 0;
+    background-color: #1e1f1c;
+    text-align: right;
+    user-select: none;
+    border-right: 1px solid #3e3d32;
+    flex-shrink: 0;
+}
+
+.line-number {
+    font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
+    font-size: 12px;
+    line-height: 18px;
+    color: #75715e;
+}
+
+.code-editor {
+    flex: 1;
+    padding: 10px 12px;
+    background-color: #272822;
+    color: #f8f8f2;
+    border: none;
+    outline: none;
+    resize: none;
+    font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
+    font-size: 12px;
+    line-height: 18px;
+    tab-size: 4;
+    white-space: pre;
+    overflow: hidden;
+}
+
+.code-editor::selection {
+    background-color: #49483e;
+}
+
+.editor-toolbar {
+    padding: 8px 12px;
+    background-color: #1e1f1c;
+    border-top: 1px solid #3e3d32;
+    border-radius: 0 0 4px 4px;
+}
+
+.editor-toolbar .btn {
+    background-color: #3e3d32;
+    border-color: #555;
+    color: #f8f8f2;
+}
+
+.editor-toolbar .btn:hover:not(:disabled) {
+    background-color: #555;
+    border-color: #75715e;
+}
+
+.editor-toolbar .btn:disabled {
+    opacity: 0.4;
+}
+
+.editor-toolbar .material-icons {
+    color: #f8f8f2;
+    font-size: 16px;
+}
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.html b/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.html
new file mode 100644
index 000000000..969df3dc5
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.html
@@ -0,0 +1,65 @@
+<div class="panel panel-default" style="margin-top: 10px;">
+    <div class="panel-heading">
+        <h4 class="panel-title">{{title}} <span class="pull-right">{{titleSuffix}}</span></h4>
+    </div>
+    @if (serviceDefinitionContent) {
+        <div class="panel-body" style="padding: 0;">
+            <div class="code-editor-scroll">
+                <div class="code-editor-wrapper">
+                    <div class="code-editor-gutter">
+                        @for (line of getLineNumbers(); track line) {
+                            <div class="line-number">{{line}}</div>
+                        }
+                    </div>
+                    <textarea
+                            class="code-editor"
+                            [(ngModel)]="serviceDefinitionContent"
+                            [attr.rows]="getLineNumbers().length || 1"
+                            spellcheck="false"
+                            (input)="onChange($event.target.value)">
+                    </textarea>
+                </div>
+            </div>
+            <div class="editor-toolbar">
+                <button id="deleteServiceDefinition" (click)="deleteServiceDefConfirmModal.open('sm')"
+                        class="btn btn-default btn-sm" title="Delete Service Definition" type="submit">
+                    <span class="material-icons">delete</span>
+                </button>
+                <button id="updateServiceDefinition" (click)="updateServiceDefConfirmModal.open('sm')"
+                        class="btn btn-default btn-sm pull-right" title="Save Service Definition" type="submit">
+                    <span class="material-icons">save</span>
+                </button>
+            </div>
+        </div>
+    }
+</div>
+<app-modal (onClose)="updateServiceDefinition()" #updateServiceDefConfirmModal>
+    <div class="modal-header">
+        <h4 class="modal-title">Updating Service Definition {{titleSuffix}}</h4>
+        <button type="button" class="btn-close" (click)="updateServiceDefConfirmModal.dismiss()"></button>
+    </div>
+    <div class="modal-body">
+        Are you sure you want to update this service definition?
+    </div>
+    <div class="modal-footer">
+        <button type="button" class="btn btn-default btn-sm"
+                (click)="updateServiceDefConfirmModal.dismiss()">Cancel</button>
+        <button type="button" class="btn btn-primary btn-sm"
+                (click)="updateServiceDefConfirmModal.close()">Ok</button>
+    </div>
+</app-modal>
+<app-modal (onClose)="deleteServiceDefinition()" #deleteServiceDefConfirmModal>
+    <div class="modal-header">
+        <h4 class="modal-title">Deleting Service Definition {{titleSuffix}}</h4>
+        <button type="button" class="btn-close" (click)="deleteServiceDefConfirmModal.dismiss()"></button>
+    </div>
+    <div class="modal-body">
+        Are you sure you want to delete this service definition?
+    </div>
+    <div class="modal-footer">
+        <button type="button" class="btn btn-default btn-sm"
+                (click)="deleteServiceDefConfirmModal.dismiss()">Cancel</button>
+        <button type="button" class="btn btn-primary btn-sm"
+                (click)="deleteServiceDefConfirmModal.close()">Ok</button>
+    </div>
+</app-modal>
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.ts b/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.ts
index 8d17fa34a..d3c8b8918 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.ts
+++ b/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition-detail.component.ts
@@ -14,74 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit, ViewChild} from '@angular/core';
-import {BsModalComponent} from 'ng2-bs3-modal';
-import Swal from 'sweetalert2';
+import {Component, OnInit} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import Swal from 'sweetalert2/dist/sweetalert2.esm.all.js';
 
-import {ServiceDefinition} from './servicedefinition';
-import {ServiceDefinitionService} from './servicedefinition.service';
-import {ResourceTypesService} from '../resourcetypes/resourcetypes.service';
-
-import 'brace/theme/monokai';
-import 'brace/mode/xml';
+import {ServiceDefinition} from '../model/servicedefinition';
+import {ServiceDefinitionService} from '../service/servicedefinition.service';
+import {ResourceTypesService} from '../service/resourcetypes.service';
+import {ModalComponent} from '../utils/modal.component';
 
 @Component({
     selector: 'app-servicedefinition-detail',
-    template: `
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h4 class="panel-title">{{title}} <span class="pull-right">{{titleSuffix}}</span></h4>
-            </div>
-            <div *ngIf="serviceDefinitionContent" class="panel-body">
-                <ace-editor
-                        [(text)]="serviceDefinitionContent"
-                        [mode]="'xml'"
-                        [options]="options"
-                        [theme]="theme"
-                        style="min-height: 430px; width:100%; overflow: auto;"
-                        (textChanged)="onChange($event)">
-                </ace-editor>
-		        <div class="panel-footer">
-		            <button id="deleteServiceDefinition" (click)="deleteServiceDefConfirmModal.open('sm')"
-		                    class="btn btn-default btn-sm" type="submit">
-		                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
-		            </button>
-		            <button id="updateServiceDefinition" (click)="updateServiceDefConfirmModal.open('sm')"
-		                class="btn btn-default btn-sm pull-right" type="submit">
-		                <span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
-		            </button>
-		        </div>
-            </div>
-        </div>
-        <bs-modal (onClose)="updateServiceDefinition()" #updateServiceDefConfirmModal>
-            <bs-modal-header [showDismiss]="true">
-                <h4 class="modal-title">Updating Service Definition {{titleSuffix}}</h4>
-            </bs-modal-header>
-            <bs-modal-body>
-                Are you sure you want to update this service definition?
-            </bs-modal-body>
-            <bs-modal-footer>
-                <button type="button" class="btn btn-default btn-sm" data-dismiss="updateServiceDefConfirmModal"
-                        (click)="updateServiceDefConfirmModal.dismiss()">Cancel
-                </button>
-                <button type="button" class="btn btn-primary btn-sm" (click)="updateServiceDefConfirmModal.close()">Ok</button>
-            </bs-modal-footer>
-        </bs-modal>
-        <bs-modal (onClose)="deleteServiceDefinition()" #deleteServiceDefConfirmModal>
-            <bs-modal-header [showDismiss]="true">
-                <h4 class="modal-title">Deleting Service Definition {{titleSuffix}}</h4>
-            </bs-modal-header>
-            <bs-modal-body>
-                Are you sure you want to delete this service definition?
-            </bs-modal-body>
-            <bs-modal-footer>
-                <button type="button" class="btn btn-default btn-sm" data-dismiss="deleteServiceDefConfirmModal"
-                        (click)="deleteServiceDefConfirmModal.dismiss()">Cancel
-                </button>
-                <button type="button" class="btn btn-primary btn-sm" (click)="deleteServiceDefConfirmModal.close()">Ok</button>
-            </bs-modal-footer>
-        </bs-modal>
-    `
+    templateUrl: './servicedefinition-detail.component.html',
+    styleUrls: ['./servicedefinition-detail.component.css'],
+    imports: [FormsModule, ModalComponent]
 })
 
 export class ServiceDefinitionDetailComponent implements OnInit {
@@ -90,13 +36,6 @@ export class ServiceDefinitionDetailComponent implements OnInit {
     titleSuffix: string;
     serviceDefinitionContent: string;
     changedServiceDefinitionContent: string;
-    theme: String = 'monokai';
-    options: any = {useWorker: false, printMargin: false};
-
-    @ViewChild('editor') editor;
-
-    @ViewChild('deleteServiceDefConfirmModal')
-    deleteServiceDefConfirmModal: BsModalComponent;
 
     constructor(private serviceDefinitionService: ServiceDefinitionService, private resourceTypesService: ResourceTypesService) {
     }
@@ -105,6 +44,14 @@ export class ServiceDefinitionDetailComponent implements OnInit {
         this.serviceDefinitionService.selectedServiceDefinition$.subscribe(value => this.populateContent(value));
     }
 
+    getLineNumbers(): number[] {
+        if (!this.serviceDefinitionContent) {
+            return [];
+        }
+        const count = this.serviceDefinitionContent.split('\n').length;
+        return Array.from({length: count}, (_, i) => i + 1);
+    }
+
     setTitleSuffix(value: string) {
         this.titleSuffix = value;
     }
@@ -125,7 +72,7 @@ export class ServiceDefinitionDetailComponent implements OnInit {
     updateServiceDefinition() {
         this.serviceDefinitionService.updateServiceDefinition(this.changedServiceDefinitionContent ? this.changedServiceDefinitionContent
                                                                 : this.serviceDefinitionContent)
-            .then(response => {
+            .then(() => {
                 Swal.fire({
                     text: 'Updated successfully!',
                     confirmButtonColor: '#7cd1f9'
@@ -137,7 +84,7 @@ export class ServiceDefinitionDetailComponent implements OnInit {
 
     deleteServiceDefinition() {
         this.serviceDefinitionService.deleteServiceDefinition(this.serviceDefinition)
-            .then(response => {
+            .then(() => {
                 Swal.fire({
                     text: 'Deleted successfully!',
                     confirmButtonColor: '#7cd1f9'
diff --git a/gateway-admin-ui/admin-ui/app/gateway-version.service.ts b/gateway-admin-ui/admin-ui/app/service/gateway-version.service.ts
similarity index 77%
rename from gateway-admin-ui/admin-ui/app/gateway-version.service.ts
rename to gateway-admin-ui/admin-ui/app/service/gateway-version.service.ts
index 895db02f8..490d9a5a4 100644
--- a/gateway-admin-ui/admin-ui/app/gateway-version.service.ts
+++ b/gateway-admin-ui/admin-ui/app/service/gateway-version.service.ts
@@ -15,13 +15,12 @@
  * limitations under the License.
  */
 import {Injectable} from '@angular/core';
-import {HttpClient, HttpHeaders} from '@angular/common/http';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import {firstValueFrom} from 'rxjs';
 
-import 'rxjs/add/operator/toPromise';
+import {GatewayVersion} from '../model/gateway-version';
 
-import {GatewayVersion} from './gateway-version';
-
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class GatewayVersionService {
     private apiUrl = window.location.pathname.replace(new RegExp('admin-ui/.*'), 'api/v1/');
     private versionUrl = this.apiUrl + 'version';
@@ -29,15 +28,11 @@ export class GatewayVersionService {
     constructor(private http: HttpClient) {
     }
 
-    getVersion(): Promise<GatewayVersion> {
+    async getVersion(): Promise<GatewayVersion> {
         let headers = new HttpHeaders();
         headers = this.addHeaders(headers);
-        return this.http.get(this.versionUrl, {headers: headers})
-            .toPromise()
-            .then(response => {
-                return response['ServerVersion'] as GatewayVersion;
-            })
-            .catch(this.handleError);
+        const response = await firstValueFrom(this.http.get(this.versionUrl, {headers: headers}));
+        return response['ServerVersion'] as GatewayVersion;
     }
 
     addHeaders(headers: HttpHeaders) {
diff --git a/gateway-admin-ui/admin-ui/app/resource/resource.service.ts b/gateway-admin-ui/admin-ui/app/service/resource.service.ts
similarity index 80%
rename from gateway-admin-ui/admin-ui/app/resource/resource.service.ts
rename to gateway-admin-ui/admin-ui/app/service/resource.service.ts
index 0b1bbdd65..ce4cfbda7 100644
--- a/gateway-admin-ui/admin-ui/app/resource/resource.service.ts
+++ b/gateway-admin-ui/admin-ui/app/service/resource.service.ts
@@ -15,14 +15,14 @@
  * limitations under the License.
  */
 import {Injectable} from '@angular/core';
-import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
-import 'rxjs/add/operator/toPromise';
-import {Subject} from 'rxjs/Subject';
-import {Resource} from './resource';
-import {ProviderConfig} from '../resource-detail/provider-config';
-import {Descriptor} from '../resource-detail/descriptor';
-
-@Injectable()
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
+import { Subject, firstValueFrom } from 'rxjs';
+import {Resource} from '../model/resource';
+import {ProviderConfig} from '../model/provider-config';
+import {Descriptor} from '../model/descriptor';
+import * as yaml from 'js-yaml';
+
+@Injectable({providedIn: 'root'})
 export class ResourceService {
     discoveryTypes: Array<string>;
 
@@ -39,11 +39,6 @@ export class ResourceService {
     selectedResourceSource = new Subject<Resource>();
     selectedResource$ = this.selectedResourceSource.asObservable();
 
-    changedResourceSource = new Subject<string>();
-    changedResource$ = this.changedResourceSource.asObservable();
-
-    changedProviderConfigurationSource = new Subject<Array<ProviderConfig>>();
-    changedProviderConfiguration$ = this.changedProviderConfigurationSource.asObservable();
 
     constructor(private http: HttpClient) {
         this.initSupportedDiscoveryTypes();
@@ -51,7 +46,6 @@ export class ResourceService {
 
     initSupportedDiscoveryTypes(): void {
         if (this.discoveryTypes == null || this.discoveryTypes.length === 0) {
-            let headers = this.addJsonHeaders(new HttpHeaders());
             this.getServiceDiscoveryResources()
             .then(response => this.discoveryTypes = response.knoxServiceDiscoveries.knoxServiceDiscovery.map(sd => sd.type))
             .catch((err: HttpErrorResponse) => {
@@ -67,7 +61,7 @@ export class ResourceService {
 
     getServiceDiscoveryResources(): Promise<any> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        return this.http.get(this.serviceDiscoveriesUrl, {headers: headers}).toPromise();
+        return firstValueFrom(this.http.get(this.serviceDiscoveriesUrl, {headers: headers}));
     }
 
     getResources(resType: string): Promise<Resource[]> {
@@ -89,61 +83,53 @@ export class ResourceService {
 
     getProviderConfigResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        return this.http.get(this.providersUrl, {headers: headers})
-            .toPromise()
+        return firstValueFrom(this.http.get(this.providersUrl, {headers: headers}))
             .then(response => response['items'] as Resource[])
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> getProviderConfigResources() --> error: HTTP ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     getDescriptorResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        return this.http.get(this.descriptorsUrl, {headers: headers})
-            .toPromise()
+        return firstValueFrom(this.http.get(this.descriptorsUrl, {headers: headers}))
             .then(response => response['items'] as Resource[])
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> getDescriptorResources() --> error: HTTP ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     getTopologyResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        return this.http.get(this.topologiesUrl, {headers: headers})
-            .toPromise()
+        return firstValueFrom(this.http.get(this.topologiesUrl, {headers: headers}))
             .then(response => response['topologies'].topology as Resource[])
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> getTopologyResources() --> error: HTTP ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     getServiceDefinitionResources(): Promise<Resource[]> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-        return this.http.get(this.serviceDefinitionsUrl + '?serviceOnly=true', {headers: headers})
-            .toPromise()
+        return firstValueFrom(this.http.get(this.serviceDefinitionsUrl + '?serviceOnly=true', {headers: headers}))
             .then(response => response['serviceDefinitions'].serviceDefinition as Resource[])
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> getServiceDefinitionResources() --> error: HTTP ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
@@ -154,11 +140,9 @@ export class ResourceService {
                 href = this.serviceDefinitionsUrl + '/' + res.service['name'] + '/' + res.service['role'] + '/' + res.service['version']
                     + '?serviceOnly=true';
             }
-            let headers = new HttpHeaders();
-            headers = (resType === 'Topologies' || resType === 'Service Definitions') ? this.addXmlHeaders(headers)
-                : this.addHeaders(headers, res.name);
-            return this.http.get(href, {headers: headers, responseType: 'text'})
-                .toPromise()
+            let headers = (resType === 'Topologies' || resType === 'Service Definitions') ? this.addXmlHeaders(new HttpHeaders())
+                : this.addHeaders(new HttpHeaders(), res.name);
+            return firstValueFrom(this.http.get(href, {headers: headers, responseType: 'text'}))
                 .then(response => {
                     console.debug('ResourceService --> Loading resource ' + res.name + ' :\n' + response);
                     return response;
@@ -167,9 +151,8 @@ export class ResourceService {
                     console.debug('ResourceService --> getResource() ' + res.name + '\n  error: ' + err.status + ' ' + err.message);
                     if (err.status === 401) {
                         window.location.assign(document.location.pathname);
-                    } else {
-                        return this.handleError(err);
                     }
+                    return this.handleError(err);
                 });
         } else {
             return Promise.resolve(null);
@@ -178,52 +161,41 @@ export class ResourceService {
 
     saveResource(resource: Resource, content: string): Promise<string> {
         let headers = this.addHeaders(new HttpHeaders(), resource.name);
-
         console.debug('ResourceService --> Persisting ' + resource.name + '\n' + content);
-
-        return this.http.put(resource.href, content, {headers: headers})
-            .toPromise()
+        return firstValueFrom(this.http.put(resource.href, content, {headers: headers, responseType: 'text'}))
             .then(() => content)
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> saveResource() ' + resource.name + '\n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     createResource(resType: string, resource: Resource, content: string): Promise<string> {
         let headers = this.addHeaders(new HttpHeaders(), resource.name);
-
         let url = ((resType === 'Descriptors') ? this.descriptorsUrl : this.providersUrl) + '/' + resource.name;
-        return this.http.put(url, content, {headers: headers})
-            .toPromise()
+        return firstValueFrom(this.http.put(url, content, {headers: headers, responseType: 'text'}))
             .then(() => content)
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> createResource() --> ' + resource.name + '\n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     deleteResource(href: string): Promise<string> {
         let headers = this.addJsonHeaders(new HttpHeaders());
-
-        return this.http.delete(href, {headers: headers})
-            .toPromise()
-            .then(response => response)
+        return firstValueFrom(this.http.delete(href, {headers: headers, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
                 console.debug('ResourceService --> deleteResource() --> ' + href + '\n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
@@ -278,8 +250,7 @@ export class ResourceService {
                 break;
             }
             case 'yaml': {
-                let yaml = require('js-yaml');
-                serialized = '---\n' + yaml.safeDump(tmp);
+                serialized = '---\n' + yaml.dump(tmp);
                 break;
             }
         }
@@ -300,7 +271,6 @@ export class ResourceService {
                 break;
             }
             case 'yaml': {
-                let yaml = require('js-yaml');
                 serialized = '---\n' + yaml.dump(tmp);
                 break;
             }
@@ -361,13 +331,6 @@ export class ResourceService {
         this.selectedResourceSource.next(value);
     }
 
-    resourceChanged(value: string) {
-        this.changedResourceSource.next(value);
-    }
-
-    providerConfigurationChanged(pc: Array<ProviderConfig>) {
-        this.changedProviderConfigurationSource.next(pc);
-    }
 
     public getResourceDisplayName(res: Resource): string {
         if (res) {
@@ -385,13 +348,6 @@ export class ResourceService {
         return Promise.reject(error);
     }
 
-    private logHeaders(headers: HttpHeaders) {
-        let debugMsg = 'ResourceService --> Request headers:\n';
-        headers.keys().forEach(key => {
-            debugMsg += '  ' + key + '=' + headers.get(key) + '\n';
-        });
-        console.debug(debugMsg);
-    }
 
 }
 
diff --git a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.service.ts b/gateway-admin-ui/admin-ui/app/service/resourcetypes.service.ts
similarity index 95%
rename from gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.service.ts
rename to gateway-admin-ui/admin-ui/app/service/resourcetypes.service.ts
index cc2dc4f0a..dd3785eec 100644
--- a/gateway-admin-ui/admin-ui/app/resourcetypes/resourcetypes.service.ts
+++ b/gateway-admin-ui/admin-ui/app/service/resourcetypes.service.ts
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 import {Injectable} from '@angular/core';
-import {Subject} from 'rxjs/Subject';
+import { Subject } from 'rxjs';
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class ResourceTypesService {
 
     resourceTypes = ['Provider Configurations', 'Descriptors', 'Topologies', 'Service Definitions'];
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition.service.ts b/gateway-admin-ui/admin-ui/app/service/servicedefinition.service.ts
similarity index 53%
rename from gateway-admin-ui/admin-ui/app/service-definition/servicedefinition.service.ts
rename to gateway-admin-ui/admin-ui/app/service/servicedefinition.service.ts
index 5202d3515..5e8898d7f 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/servicedefinition.service.ts
+++ b/gateway-admin-ui/admin-ui/app/service/servicedefinition.service.ts
@@ -15,119 +15,79 @@
  * limitations under the License.
  */
 import {Injectable} from '@angular/core';
-import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
-import {RestURLBuilder} from 'rest-url-builder';
-import Swal from 'sweetalert2';
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
+import Swal from 'sweetalert2/dist/sweetalert2.esm.all.js';
 
-import 'rxjs/add/operator/toPromise';
-import {Subject} from 'rxjs/Subject';
+import { Subject, firstValueFrom } from 'rxjs';
 
-import {ServiceDefinition} from './servicedefinition';
+import {ServiceDefinition} from '../model/servicedefinition';
 
- @Injectable()
+ @Injectable({providedIn: 'root'})
 export class ServiceDefinitionService {
     apiUrl = window.location.pathname.replace(new RegExp('admin-ui/.*'), 'api/v1/');
     serviceDefinitionsBaseUrl = this.apiUrl + 'servicedefinitions';
-    serviceDefinitionUrlTemplate = this.serviceDefinitionsBaseUrl + '/:service/:role/:version';
     selectedServiceDefinitionSource = new Subject<ServiceDefinition>();
     selectedServiceDefinition$ = this.selectedServiceDefinitionSource.asObservable();
-    urlBuilder = new RestURLBuilder();
 
     constructor(private http: HttpClient) {
     }
 
-    getServiceDefinitions(): Promise<ServiceDefinition[]> {
-        let headers = new HttpHeaders();
-        headers = this.addXmlHeaders(headers);
-        return this.http.get(this.serviceDefinitionsBaseUrl, { headers: headers})
-            .toPromise()
-            .then(response => response['serviceDefinitions'].serviceDefinition as ServiceDefinition[])
+getServiceDefinitionXml(serviceDefinition: ServiceDefinition): Promise<string> {
+        let headers = this.addXmlHeaders(new HttpHeaders());
+        let url = this.serviceDefinitionsBaseUrl + '/' + serviceDefinition.service
+            + '/' + serviceDefinition.role + '/' + serviceDefinition.version;
+        return firstValueFrom(this.http.get(url, {headers: headers, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
-                console.debug('ServiceDefinitionService --> getServiceDefinitions() --> ' + this.serviceDefinitionsBaseUrl
+                console.debug('ServiceDefinitionService --> getServiceDefinitionXml() --> ' + url
                               + '\n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
-                }
-            });
-    }
-
-    getServiceDefinitionXml(serviceDefinition: ServiceDefinition) {
-        let headers = new HttpHeaders();
-        headers = this.addXmlHeaders(headers);
-        let urlBuilder = this.urlBuilder.buildRestURL(this.serviceDefinitionUrlTemplate);
-        urlBuilder.setNamedParameter('service', serviceDefinition.service);
-        urlBuilder.setNamedParameter('role', serviceDefinition.role);
-        urlBuilder.setNamedParameter('version', serviceDefinition.version);
-        return this.http.get(urlBuilder.get(), {headers: headers, responseType: 'text'})
-            .toPromise()
-            .then(response => response)
-            .catch((err: HttpErrorResponse) => {
-                console.debug('ServiceDefinitionService --> getServiceDefinitionXml() --> ' + urlBuilder.get()
-                              + '\n  error: ' + err.message);
-                if (err.status === 401) {
-                    window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     saveNewServiceDefinition(xml: string): Promise<string> {
-        let xheaders = new HttpHeaders();
-        xheaders = this.addXmlHeaders(xheaders);
+        let xheaders = this.addXmlHeaders(new HttpHeaders());
         let serviceDefintionXml = xml.replace('<serviceDefinitions>', '').replace('</serviceDefinitions>', '');
-        return this.http.post(this.serviceDefinitionsBaseUrl, serviceDefintionXml, {headers: xheaders, responseType: 'text'})
-            .toPromise()
-            .then(response => response)
+        return firstValueFrom(
+            this.http.post(this.serviceDefinitionsBaseUrl, serviceDefintionXml, {headers: xheaders, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
                 console.debug('ServiceDefinitionService --> saveNewServiceDefinition() --> ' + this.serviceDefinitionsBaseUrl
                               + '\n  error: ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     updateServiceDefinition(xml: string): Promise<string> {
-        let xheaders = new HttpHeaders();
-        xheaders = this.addXmlHeaders(xheaders);
+        let xheaders = this.addXmlHeaders(new HttpHeaders());
         let serviceDefintionXml = xml.replace('<serviceDefinitions>', '').replace('</serviceDefinitions>', '');
-        return this.http.put(this.serviceDefinitionsBaseUrl, serviceDefintionXml, {headers: xheaders, responseType: 'text'})
-            .toPromise()
-            .then(response => response)
+        return firstValueFrom(this.http.put(this.serviceDefinitionsBaseUrl, serviceDefintionXml, {headers: xheaders, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
                 console.debug('ServiceDefinitionService --> updateServiceDefinition() --> ' + this.serviceDefinitionsBaseUrl
                               + '\n  error: ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     deleteServiceDefinition(serviceDefinition: ServiceDefinition): Promise<string> {
-        let xheaders = new HttpHeaders();
-        xheaders = this.addXmlHeaders(xheaders);
-        let urlBuilder = this.urlBuilder.buildRestURL(this.serviceDefinitionUrlTemplate);
-        urlBuilder.setNamedParameter('service', serviceDefinition.service);
-        urlBuilder.setNamedParameter('role', serviceDefinition.role);
-        urlBuilder.setNamedParameter('version', serviceDefinition.version);
-        return this.http.delete(urlBuilder.get(), {headers: xheaders, responseType: 'text'})
-            .toPromise()
-            .then(response => response)
+        let xheaders = this.addXmlHeaders(new HttpHeaders());
+        let url = this.serviceDefinitionsBaseUrl + '/' + serviceDefinition.service
+            + '/' + serviceDefinition.role + '/' + serviceDefinition.version;
+        return firstValueFrom(this.http.delete(url, {headers: xheaders, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
-                console.debug('ServiceDefinitionService --> deleteServiceDefinition() --> ' + urlBuilder.get()
+                console.debug('ServiceDefinitionService --> deleteServiceDefinition() --> ' + url
                               + '\n  error: ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
@@ -135,11 +95,6 @@ export class ServiceDefinitionService {
         this.selectedServiceDefinitionSource.next(value);
     }
 
-    addJsonHeaders(headers: HttpHeaders): HttpHeaders {
-        return this.addCsrfHeaders(headers.append('Accept', 'application/json')
-            .append('Content-Type', 'application/json'));
-    }
-
     addXmlHeaders(headers: HttpHeaders): HttpHeaders {
         return this.addCsrfHeaders(headers.append('Accept', 'application/xml')
             .append('Content-Type', 'application/xml'));
diff --git a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.service.ts b/gateway-admin-ui/admin-ui/app/service/session.information.service.ts
similarity index 83%
rename from gateway-admin-ui/admin-ui/app/sessionInformation/session.information.service.ts
rename to gateway-admin-ui/admin-ui/app/service/session.information.service.ts
index 95a3cdc8f..50d4af3a3 100644
--- a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.service.ts
+++ b/gateway-admin-ui/admin-ui/app/service/session.information.service.ts
@@ -15,13 +15,13 @@
  * limitations under the License.
  */
 import {Injectable} from '@angular/core';
-import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
-import Swal from 'sweetalert2';
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
+import {firstValueFrom} from 'rxjs';
+import Swal from 'sweetalert2/dist/sweetalert2.esm.all.js';
 
-import 'rxjs/add/operator/toPromise';
-import {SessionInformation} from './session.information';
+import {SessionInformation} from '../model/session-information';
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class SessionInformationService {
     pathParts = window.location.pathname.split('/');
     topologyContext = '/' + this.pathParts[1] + '/' + this.pathParts[2] + '/';
@@ -30,18 +30,15 @@ export class SessionInformationService {
     constructor(private http: HttpClient) {}
 
     getSessionInformation(): Promise<SessionInformation> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(this.sessionUrl, { headers: headers})
-            .toPromise()
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(this.sessionUrl, {headers: headers}))
             .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);
                 }
+                return this.handleError(err);
             });
     }
 
diff --git a/gateway-admin-ui/admin-ui/app/topology.service.ts b/gateway-admin-ui/admin-ui/app/service/topology.service.ts
similarity index 67%
rename from gateway-admin-ui/admin-ui/app/topology.service.ts
rename to gateway-admin-ui/admin-ui/app/service/topology.service.ts
index a4831a63d..0c0fc1211 100644
--- a/gateway-admin-ui/admin-ui/app/topology.service.ts
+++ b/gateway-admin-ui/admin-ui/app/service/topology.service.ts
@@ -15,13 +15,12 @@
  * limitations under the License.
  */
 import {Injectable} from '@angular/core';
-import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
+import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
 
-import 'rxjs/add/operator/toPromise';
-import {Subject} from 'rxjs/Subject';
-import {Topology} from './topology';
+import { Subject, firstValueFrom } from 'rxjs';
+import {Topology} from '../model/topology';
 
-@Injectable()
+@Injectable({providedIn: 'root'})
 export class TopologyService {
     apiUrl = window.location.pathname.replace(new RegExp('admin-ui/.*'), 'api/v1/');
     topologiesUrl = this.apiUrl + 'topologies';
@@ -34,89 +33,66 @@ export class TopologyService {
     }
 
     getTopologies(): Promise<Topology[]> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.get(this.topologiesUrl, {
-            headers: headers
-        })
-            .toPromise()
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(this.topologiesUrl, {headers: headers}))
             .then(response => response['topologies'].topology as Topology[])
             .catch((err: HttpErrorResponse) => {
-                console.debug('TopologyService --> getTopologies() --> ' + '\n  error: ' + err.message);
+                console.debug('TopologyService --> getTopologies() --> \n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     getTopology(href: string): Promise<string> {
-        let headers = new HttpHeaders();
-        headers = this.addXmlHeaders(headers);
-        return this.http.get(href, {
-            headers: headers, responseType: 'text'
-        })
-            .toPromise()
-            .then(response => response)
+        let headers = this.addXmlHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.get(href, {headers: headers, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
                 console.debug('TopologyService --> getTopology() --> ' + href + '\n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     saveTopology(url: string, xml: string): Promise<string> {
-        let xheaders = new HttpHeaders();
-        xheaders = this.addXmlHeaders(xheaders);
-        return this.http.put(url, xml, {headers: xheaders, responseType: 'text'})
-            .toPromise()
+        let xheaders = this.addXmlHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.put(url, xml, {headers: xheaders, responseType: 'text'}))
             .then(() => xml)
             .catch((err: HttpErrorResponse) => {
-                console.debug('TopologyService --> getTopology() --> \n  error: ' + err.status + ' ' + err.message);
+                console.debug('TopologyService --> saveTopology() --> \n  error: ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     createTopology(name: string, xml: string): Promise<string> {
-        let xheaders = new HttpHeaders();
-        xheaders = this.addXmlHeaders(xheaders);
+        let xheaders = this.addXmlHeaders(new HttpHeaders());
         let url = this.topologiesUrl + '/' + name;
-        return this.http.put(url, xml, {headers: xheaders, responseType: 'text'})
-            .toPromise()
+        return firstValueFrom(this.http.put(url, xml, {headers: xheaders, responseType: 'text'}))
             .then(() => xml)
             .catch((err: HttpErrorResponse) => {
                 console.debug('TopologyService --> createTopology() --> \n  error: ' + err.status + ' ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
     deleteTopology(href: string): Promise<string> {
-        let headers = new HttpHeaders();
-        headers = this.addJsonHeaders(headers);
-        return this.http.delete(href, {
-            headers: headers, responseType: 'text'
-        })
-            .toPromise()
-            .then(response => response)
+        let headers = this.addJsonHeaders(new HttpHeaders());
+        return firstValueFrom(this.http.delete(href, {headers: headers, responseType: 'text'}))
             .catch((err: HttpErrorResponse) => {
-                console.debug('TopologyService --> getTopology() --> ' + href + '\n  error: ' + err.message);
+                console.debug('TopologyService --> deleteTopology() --> ' + href + '\n  error: ' + err.message);
                 if (err.status === 401) {
                     window.location.assign(document.location.pathname);
-                } else {
-                    return this.handleError(err);
                 }
+                return this.handleError(err);
             });
     }
 
diff --git a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.component.html b/gateway-admin-ui/admin-ui/app/session-information/session.information.component.html
similarity index 100%
rename from gateway-admin-ui/admin-ui/app/sessionInformation/session.information.component.html
rename to gateway-admin-ui/admin-ui/app/session-information/session.information.component.html
diff --git a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.component.ts b/gateway-admin-ui/admin-ui/app/session-information/session.information.component.ts
similarity index 91%
rename from gateway-admin-ui/admin-ui/app/sessionInformation/session.information.component.ts
rename to gateway-admin-ui/admin-ui/app/session-information/session.information.component.ts
index e21e61264..9f7bd7bb0 100644
--- a/gateway-admin-ui/admin-ui/app/sessionInformation/session.information.component.ts
+++ b/gateway-admin-ui/admin-ui/app/session-information/session.information.component.ts
@@ -16,10 +16,12 @@
  */
 import {Component, OnInit, Pipe, PipeTransform} from '@angular/core';
 import {DomSanitizer} from '@angular/platform-browser';
-import {SessionInformationService} from './session.information.service';
-import {SessionInformation} from './session.information';
+import {SessionInformationService} from '../service/session.information.service';
+import {SessionInformation} from '../model/session-information';
 
-@Pipe({ name: 'safeHtml' })
+@Pipe({
+    name: 'safeHtml'
+})
 export class SafeHtmlPipe implements PipeTransform {
   constructor(private sanitizer: DomSanitizer) {}
 
@@ -31,13 +33,12 @@ export class SafeHtmlPipe implements PipeTransform {
 @Component({
     selector: 'app-session-information',
     templateUrl: './session.information.component.html',
-    providers: [SessionInformationService]
+    imports: [SafeHtmlPipe]
 })
 
 export class SessionInformationComponent implements OnInit {
 
     sessionInformation: SessionInformation;
-    logoutSupported = true;
 
     constructor(private sessionInformationService: SessionInformationService) {
         this['showSessionInformation'] = true;
diff --git a/gateway-admin-ui/admin-ui/app/topology-detail.component.ts b/gateway-admin-ui/admin-ui/app/topology-detail.component.ts
deleted file mode 100644
index f1e4f98be..000000000
--- a/gateway-admin-ui/admin-ui/app/topology-detail.component.ts
+++ /dev/null
@@ -1,209 +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 {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
-import {Topology} from './topology';
-import {TopologyService} from './topology.service';
-import {ResourceTypesService} from './resourcetypes/resourcetypes.service';
-import {BsModalComponent} from 'ng2-bs3-modal';
-import {ValidationUtils} from './utils/validation-utils';
-import * as ace from 'ace-builds';
-
-@Component({
-    selector: 'app-topology-detail',
-    template: `
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <h4 class="panel-title">{{title}} <span *ngIf="showEditOptions === false" style="padding-left: 15%;"
-                                                        class="text-danger text-center"> Read Only (generated file) </span> <span
-                        class="pull-right">{{titleSuffix}}</span></h4>
-            </div>
-            <div [style.display]="getEditorVisibility()" class="panel-body">
-                <ace-editor
-                        #editor
-                        [(text)]="topologyContent"
-                        [options]="options"
-                        [theme]="theme"
-                        [readOnly]="!showEditOptions"
-                        style="min-height: 430px; width:100%; overflow: auto;"
-                        (textChanged)="onChange($event)">
-                </ace-editor>
-                <div class="panel-footer">
-                    <button id="duplicateTopology" (click)="duplicateModal.open('sm')" class="btn btn-default btn-sm" type="submit">
-                        <span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span>
-                    </button>
-                    <button id="deleteTopology" *ngIf="showEditOptions" (click)="deleteConfirmModal.open('sm')"
-                            class="btn btn-default btn-sm" type="submit">
-                        <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
-                    </button>
-                    <button id="saveTopology" *ngIf="showEditOptions" (click)="saveTopology()" class="btn btn-default btn-sm pull-right"
-                            [disabled]="!changedTopology" type="submit">
-                        <span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
-                    </button>
-                </div>
-            </div>
-            <bs-modal (onClose)="createTopology()" #duplicateModal>
-                <bs-modal-header [showDismiss]="true">
-                    <h4 class="modal-title">Create a copy</h4>
-                </bs-modal-header>
-                <bs-modal-body>
-                    <div class="form-group">
-                        <label for="textbox">Name the new topology</label>
-                        <input autofocus type="text" class="form-control" required [(ngModel)]="newTopologyName" id="textbox">
-                    </div>
-                </bs-modal-body>
-                <bs-modal-footer>
-                    <button type="button" class="btn btn-default btn-sm" data-dismiss="duplicateModal" (click)="duplicateModal.dismiss()">
-                        Cancel
-                    </button>
-                    <button type="button" class="btn btn-primary btn-sm" [disabled]="!isValidNewTopologyName()"
-                            (click)="duplicateModal.close()">Ok
-                    </button>
-                </bs-modal-footer>
-            </bs-modal>
-            <bs-modal (onClose)="deleteTopology()" #deleteConfirmModal>
-                <bs-modal-header [showDismiss]="true">
-                    <h4 class="modal-title">Deleting Topology {{titleSuffix}}</h4>
-                </bs-modal-header>
-                <bs-modal-body>
-                    Are you sure you want to delete the topology?
-                </bs-modal-body>
-                <bs-modal-footer>
-                    <button type="button" class="btn btn-default btn-sm" data-dismiss="deleteConfirmModal"
-                            (click)="deleteConfirmModal.dismiss()">Cancel
-                    </button>
-                    <button type="button" class="btn btn-primary btn-sm" (click)="deleteConfirmModal.close()">Ok</button>
-                </bs-modal-footer>
-            </bs-modal>
-        </div>
-    `
-})
-export class TopologyDetailComponent implements OnInit, AfterViewInit {
-    title = 'Topology Detail';
-    titleSuffix: string;
-    topology: Topology;
-    topologyContent: string;
-    changedTopology: string;
-    newTopologyName: string;
-    showEditOptions = true;
-    theme: String = 'monokai';
-    options: any = {useWorker: false, printMargin: false};
-
-    @ViewChild('duplicateModal')
-    duplicateModal: BsModalComponent;
-
-    @ViewChild('deleteConfirmModal')
-    deleteConfirmModal: BsModalComponent;
-
-    @ViewChild('editor')
-    editor: ElementRef<HTMLElement>;
-
-    constructor(private topologyService: TopologyService, private resourceTypesService: ResourceTypesService) {
-    }
-
-    ngOnInit(): void {
-        this.topologyService.selectedTopology$.subscribe(value => this.populateContent(value));
-    }
-
-    ngAfterViewInit(): void {
-        ace.config.set(
-            'basePath',
-            'https://unpkg.com/ace-builds@1.4.12/src-noconflict'
-        );
-        const aceEditor = ace.edit(this.editor.nativeElement);
-        aceEditor.session.setMode('xml');
-    }
-
-    getEditorVisibility(): string {
-        return this.topologyContent ? 'block' : 'none';
-    }
-
-    setTitle(value: string) {
-        this.titleSuffix = value;
-    }
-
-    onChange(code: any) {
-        this.changedTopology = code;
-    }
-
-    saveTopology() {
-        this.topologyService.saveTopology(this.topology.href, this.changedTopology)
-            .then(value => this.topologyService.changedTopology(this.topology.name));
-    }
-
-    createTopology() {
-        this.topologyService.createTopology(this.newTopologyName,
-            (this.changedTopology ? this.changedTopology : this.topologyContent))
-            .then(() => {
-                this.topologyService.changedTopology(this.newTopologyName);
-                // This refreshes the list of topologies
-                this.resourceTypesService.selectResourceType('Topologies');
-            });
-    }
-
-    deleteTopology() {
-        this.topologyService.deleteTopology(this.topology.href)
-            .then(() => {
-                this.topologyService.changedTopology(this.topology.name);
-                // This refreshes the list of topologies
-                this.resourceTypesService.selectResourceType('Topologies');
-                // This refreshes the topology content panel to the first one in the list
-                this.topologyService.getTopologies()
-                    .then(topologies => {
-                        this.topologyService.selectedTopology(topologies[0]);
-                    });
-            });
-    }
-
-    populateContent(topology: Topology) {
-        this.topology = topology;
-        this.setTitle(topology.name);
-        if (this.topology) {
-            if (this.topology.href) {
-                this.topologyService.getTopology(this.topology.href)
-                    .then(content => this.topologyContent = content)
-                    .then(() => this.makeReadOnly(this.topologyContent, 'generated'));
-            }
-        }
-    }
-
-    isValidNewTopologyName(): boolean {
-        return ValidationUtils.isValidResourceName(this.newTopologyName);
-    }
-
-    /*
-    * Parse the XML and depending on the
-    * provided tag value make the editor read only
-    */
-    makeReadOnly(text, tag) {
-        let parser = new DOMParser();
-        let parsed = parser.parseFromString(text, 'text/xml');
-
-        let tagValue = parsed.getElementsByTagName(tag);
-        let result = tagValue[0].childNodes[0].nodeValue;
-
-        if (result === 'true') {
-            this.showEditOptions = false;
-            this.options = {readOnly: true, useWorker: false, printMargin: false, highlightActiveLine: false, highlightGutterLine: false};
-        } else {
-            this.showEditOptions = true;
-            this.options = {readOnly: false, useWorker: false, printMargin: false};
-        }
-
-    }
-
-
-}
diff --git a/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.css b/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.css
new file mode 100644
index 000000000..ff44ea768
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.css
@@ -0,0 +1,80 @@
+.code-editor-scroll {
+    height: 65vh;
+    overflow: auto;
+    background-color: #272822;
+    border-radius: 4px 4px 0 0;
+}
+
+.code-editor-wrapper {
+    display: flex;
+    align-items: stretch;
+    min-height: 100%;
+}
+
+.code-editor-gutter {
+    min-width: 40px;
+    padding: 10px 8px 10px 0;
+    background-color: #1e1f1c;
+    text-align: right;
+    user-select: none;
+    border-right: 1px solid #3e3d32;
+    flex-shrink: 0;
+}
+
+.line-number {
+    font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
+    font-size: 12px;
+    line-height: 18px;
+    color: #75715e;
+}
+
+.code-editor {
+    flex: 1;
+    padding: 10px 12px;
+    background-color: #272822;
+    color: #f8f8f2;
+    border: none;
+    outline: none;
+    resize: none;
+    font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
+    font-size: 12px;
+    line-height: 18px;
+    tab-size: 4;
+    white-space: pre;
+    overflow: hidden;
+}
+
+.code-editor:read-only {
+    opacity: 0.85;
+}
+
+.code-editor::selection {
+    background-color: #49483e;
+}
+
+.editor-toolbar {
+    padding: 8px 12px;
+    background-color: #1e1f1c;
+    border-top: 1px solid #3e3d32;
+    border-radius: 0 0 4px 4px;
+}
+
+.editor-toolbar .btn {
+    background-color: #3e3d32;
+    border-color: #555;
+    color: #f8f8f2;
+}
+
+.editor-toolbar .btn:hover:not(:disabled) {
+    background-color: #555;
+    border-color: #75715e;
+}
+
+.editor-toolbar .btn:disabled {
+    opacity: 0.4;
+}
+
+.editor-toolbar .material-icons {
+    color: #f8f8f2;
+    font-size: 16px;
+}
diff --git a/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.html b/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.html
new file mode 100644
index 000000000..6a4c9cd2d
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.html
@@ -0,0 +1,77 @@
+<div class="panel panel-default" style="margin-top: 10px;">
+    <div class="panel-heading">
+        <h4 class="panel-title">{{title}}
+            @if (showEditOptions === false) {
+                <span style="padding-left: 15%;" class="text-danger text-center"> Read Only (generated file) </span>
+            }
+            <span class="pull-right">{{titleSuffix}}</span>
+        </h4>
+    </div>
+    <div [style.display]="getEditorVisibility()" class="panel-body" style="padding: 0;">
+        <div class="code-editor-scroll">
+            <div class="code-editor-wrapper">
+                <div class="code-editor-gutter">
+                    @for (line of getLineNumbers(); track line) {
+                        <div class="line-number">{{line}}</div>
+                    }
+                </div>
+                <textarea
+                        class="code-editor"
+                        [(ngModel)]="topologyContent"
+                        [readOnly]="!showEditOptions"
+                        [attr.rows]="getLineNumbers().length || 1"
+                        spellcheck="false"
+                        (input)="onChange($event.target.value)">
+                </textarea>
+            </div>
+        </div>
+        <div class="editor-toolbar">
+            <button id="duplicateTopology" (click)="duplicateModal.open('sm')" class="btn btn-default btn-sm"
+                    title="Duplicate Topology" type="submit">
+                <span class="material-icons">content_copy</span>
+            </button>
+            @if (showEditOptions) {
+                <button id="deleteTopology" (click)="deleteConfirmModal.open('sm')"
+                        class="btn btn-default btn-sm" title="Delete Topology" type="submit">
+                    <span class="material-icons">delete</span>
+                </button>
+            }
+            @if (showEditOptions) {
+                <button id="saveTopology" (click)="saveTopology()" class="btn btn-default btn-sm pull-right"
+                        title="Save Topology" [disabled]="!changedTopology" type="submit">
+                    <span class="material-icons">save</span>
+                </button>
+            }
+        </div>
+    </div>
+    <app-modal (onClose)="createTopology()" #duplicateModal>
+        <div class="modal-header">
+            <h4 class="modal-title">Create a copy</h4>
+            <button type="button" class="btn-close" (click)="duplicateModal.dismiss()"></button>
+        </div>
+        <div class="modal-body">
+            <div class="form-group">
+                <label for="textbox">Name the new topology</label>
+                <input autofocus type="text" class="form-control" required [(ngModel)]="newTopologyName" id="textbox">
+            </div>
+        </div>
+        <div class="modal-footer">
+            <button type="button" class="btn btn-default btn-sm" (click)="duplicateModal.dismiss()">Cancel</button>
+            <button type="button" class="btn btn-primary btn-sm" [disabled]="!isValidNewTopologyName()"
+                    (click)="duplicateModal.close()">Ok</button>
+        </div>
+    </app-modal>
+    <app-modal (onClose)="deleteTopology()" #deleteConfirmModal>
+        <div class="modal-header">
+            <h4 class="modal-title">Deleting Topology {{titleSuffix}}</h4>
+            <button type="button" class="btn-close" (click)="deleteConfirmModal.dismiss()"></button>
+        </div>
+        <div class="modal-body">
+            Are you sure you want to delete the topology?
+        </div>
+        <div class="modal-footer">
+            <button type="button" class="btn btn-default btn-sm" (click)="deleteConfirmModal.dismiss()">Cancel</button>
+            <button type="button" class="btn btn-primary btn-sm" (click)="deleteConfirmModal.close()">Ok</button>
+        </div>
+    </app-modal>
+</div>
diff --git a/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.ts b/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.ts
new file mode 100644
index 000000000..3ac6c9efd
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/topology-detail/topology-detail.component.ts
@@ -0,0 +1,118 @@
+/*
+ * 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, OnInit} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {Topology} from '../model/topology';
+import {TopologyService} from '../service/topology.service';
+import {ResourceTypesService} from '../service/resourcetypes.service';
+import {ValidationUtils} from '../utils/validation-utils';
+import {ModalComponent} from '../utils/modal.component';
+
+@Component({
+    selector: 'app-topology-detail',
+    templateUrl: './topology-detail.component.html',
+    styleUrls: ['./topology-detail.component.css'],
+    imports: [FormsModule, ModalComponent]
+})
+export class TopologyDetailComponent implements OnInit {
+    title = 'Topology Detail';
+    titleSuffix: string;
+    topology: Topology;
+    topologyContent: string;
+    changedTopology: string;
+    newTopologyName: string;
+    showEditOptions = true;
+
+    constructor(private topologyService: TopologyService, private resourceTypesService: ResourceTypesService) {
+    }
+
+    ngOnInit(): void {
+        this.topologyService.selectedTopology$.subscribe(value => this.populateContent(value));
+    }
+
+    getEditorVisibility(): string {
+        return this.topologyContent ? 'block' : 'none';
+    }
+
+    getLineNumbers(): number[] {
+        if (!this.topologyContent) {
+            return [];
+        }
+        const count = this.topologyContent.split('\n').length;
+        return Array.from({length: count}, (_, i) => i + 1);
+    }
+
+    setTitle(value: string) {
+        this.titleSuffix = value;
+    }
+
+    onChange(code: any) {
+        this.changedTopology = code;
+    }
+
+    saveTopology() {
+        this.topologyService.saveTopology(this.topology.href, this.changedTopology)
+            .then(() => this.topologyService.changedTopology(this.topology.name));
+    }
+
+    createTopology() {
+        this.topologyService.createTopology(this.newTopologyName,
+            (this.changedTopology ? this.changedTopology : this.topologyContent))
+            .then(() => {
+                this.topologyService.changedTopology(this.newTopologyName);
+                this.resourceTypesService.selectResourceType('Topologies');
+            });
+    }
+
+    deleteTopology() {
+        this.topologyService.deleteTopology(this.topology.href)
+            .then(() => {
+                this.topologyService.changedTopology(this.topology.name);
+                this.resourceTypesService.selectResourceType('Topologies');
+                this.topologyService.getTopologies()
+                    .then(topologies => {
+                        this.topologyService.selectedTopology(topologies[0]);
+                    });
+            });
+    }
+
+    populateContent(topology: Topology) {
+        this.topology = topology;
+        this.setTitle(topology.name);
+        if (this.topology) {
+            if (this.topology.href) {
+                this.topologyService.getTopology(this.topology.href)
+                    .then(content => this.topologyContent = content)
+                    .then(() => this.makeReadOnly(this.topologyContent, 'generated'));
+            }
+        }
+    }
+
+    isValidNewTopologyName(): boolean {
+        return ValidationUtils.isValidResourceName(this.newTopologyName);
+    }
+
+    makeReadOnly(text, tag) {
+        let parser = new DOMParser();
+        let parsed = parser.parseFromString(text, 'text/xml');
+
+        let tagValue = parsed.getElementsByTagName(tag);
+        let result = tagValue[0].childNodes[0].nodeValue;
+
+        this.showEditOptions = result !== 'true';
+    }
+}
diff --git a/gateway-admin-ui/admin-ui/app/topology/topology.component.html b/gateway-admin-ui/admin-ui/app/topology/topology.component.html
new file mode 100644
index 000000000..447af8140
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/topology/topology.component.html
@@ -0,0 +1,19 @@
+<div class="table-responsive" style="max-height: 400px; width:100%; overflow: auto;">
+    <table class="table table-striped table-hover">
+        <thead>
+        <tr>
+            <th>Topology Name</th>
+            <th>Timestamp</th>
+        </tr>
+        </thead>
+        <tbody>
+        @for (topology of topologies; track topology.name) {
+            <tr [class.selected]="topology === selectedTopology"
+                (click)="onSelect(topology)">
+                <td>{{topology.name}}</td>
+                <td>{{topology.timestamp | date:'medium'}}</td>
+            </tr>
+        }
+        </tbody>
+    </table>
+</div>
diff --git a/gateway-admin-ui/admin-ui/app/topology.component.ts b/gateway-admin-ui/admin-ui/app/topology/topology.component.ts
similarity index 60%
rename from gateway-admin-ui/admin-ui/app/topology.component.ts
rename to gateway-admin-ui/admin-ui/app/topology/topology.component.ts
index bb279df21..f4338ba42 100644
--- a/gateway-admin-ui/admin-ui/app/topology.component.ts
+++ b/gateway-admin-ui/admin-ui/app/topology/topology.component.ts
@@ -15,32 +15,15 @@
  * limitations under the License.
  */
 import {Component, OnInit} from '@angular/core';
-import {Topology} from './topology';
-import {TopologyService} from './topology.service';
+import {DatePipe} from '@angular/common';
+import {Topology} from '../model/topology';
+import {TopologyService} from '../service/topology.service';
 
 
 @Component({
     selector: 'app-topology',
-    template: `
-        <div class="table-responsive" style="max-height: 400px; width:100%; overflow: auto;">
-            <table class="table table-striped table-hover">
-                <thead>
-                <tr>
-                    <th>Topology Name</th>
-                    <th>Timestamp</th>
-                </tr>
-                </thead>
-                <tbody>
-                <tr *ngFor="let topology of topologies"
-                    [class.selected]="topology === selectedTopology"
-                    (click)="onSelect(topology)">
-                    <td>{{topology.name}}</td>
-                    <td>{{topology.timestamp | date:'medium'}}</td>
-                </tr>
-                </tbody>
-            </table>
-        </div>
-    `
+    templateUrl: './topology.component.html',
+    imports: [DatePipe]
 })
 export class TopologyComponent implements OnInit {
 
@@ -58,7 +41,7 @@ export class TopologyComponent implements OnInit {
 
     ngOnInit(): void {
         this.getTopologies();
-        this.topologyService.changedTopology$.subscribe(value => this.getTopologies());
+        this.topologyService.changedTopology$.subscribe(() => this.getTopologies());
     }
 
     onSelect(topology: Topology): void {
@@ -66,4 +49,3 @@ export class TopologyComponent implements OnInit {
         this.topologyService.selectedTopology(topology);
     }
 }
-
diff --git a/gateway-admin-ui/admin-ui/app/utils/json-pretty.pipe.ts b/gateway-admin-ui/admin-ui/app/utils/json-pretty.pipe.ts
index 8d531225e..f1f5b4d6b 100644
--- a/gateway-admin-ui/admin-ui/app/utils/json-pretty.pipe.ts
+++ b/gateway-admin-ui/admin-ui/app/utils/json-pretty.pipe.ts
@@ -18,7 +18,9 @@ import {Pipe, PipeTransform} from '@angular/core';
 
 declare let vkbeautify: any;
 
-@Pipe({name: 'jsonpretty'})
+@Pipe({
+    name: 'jsonpretty'
+})
 export class JsonPrettyPipe implements PipeTransform {
 
     transform(value: string): string {
diff --git a/gateway-admin-ui/admin-ui/app/utils/modal.component.html b/gateway-admin-ui/admin-ui/app/utils/modal.component.html
new file mode 100644
index 000000000..feb4e4ee1
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/utils/modal.component.html
@@ -0,0 +1,10 @@
+<div class="modal" [class.show]="visible" [style.display]="visible ? 'block' : 'none'" tabindex="-1">
+    <div class="modal-dialog" [class.modal-sm]="size === 'sm'" [class.modal-lg]="size === 'lg'">
+        <div class="modal-content">
+            <ng-content></ng-content>
+        </div>
+    </div>
+</div>
+@if (visible) {
+    <div class="modal-backdrop fade show"></div>
+}
diff --git a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.ts b/gateway-admin-ui/admin-ui/app/utils/modal.component.ts
similarity index 61%
rename from gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.ts
rename to gateway-admin-ui/admin-ui/app/utils/modal.component.ts
index c0a447164..be6f2d0f3 100644
--- a/gateway-admin-ui/admin-ui/app/descriptor/descriptor.component.ts
+++ b/gateway-admin-ui/admin-ui/app/utils/modal.component.ts
@@ -14,16 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit} from '@angular/core';
+import {Component, EventEmitter, Input, Output} from '@angular/core';
 
 @Component({
-    selector: 'app-descriptor',
-    templateUrl: './descriptor.component.html',
-    styleUrls: ['./descriptor.component.css']
+    selector: 'app-modal',
+    templateUrl: './modal.component.html'
 })
-export class DescriptorComponent {
+export class ModalComponent {
+    @Input() size: string = 'md';
+    @Output() onClose = new EventEmitter<void>();
 
-    constructor() {
+    visible = false;
+
+    open(size?: string) {
+        if (size) {
+            this.size = size;
+        }
+        this.visible = true;
+    }
+
+    close() {
+        this.visible = false;
+        this.onClose.emit();
     }
 
+    dismiss() {
+        this.visible = false;
+    }
 }
diff --git a/gateway-admin-ui/admin-ui/app/utils/tab.component.css b/gateway-admin-ui/admin-ui/app/utils/tab.component.css
new file mode 100644
index 000000000..d5e3c5d33
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/utils/tab.component.css
@@ -0,0 +1,3 @@
+.pane {
+    padding: 1em;
+}
diff --git a/gateway-admin-ui/admin-ui/app/utils/tab.component.html b/gateway-admin-ui/admin-ui/app/utils/tab.component.html
new file mode 100644
index 000000000..ee8aa2d1b
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/utils/tab.component.html
@@ -0,0 +1,2 @@
+<div [hidden]="!active" class="pane">
+</div>
diff --git a/gateway-admin-ui/admin-ui/app/utils/tab.component.ts b/gateway-admin-ui/admin-ui/app/utils/tab.component.ts
index 2fd74bfa2..0ad871aa6 100644
--- a/gateway-admin-ui/admin-ui/app/utils/tab.component.ts
+++ b/gateway-admin-ui/admin-ui/app/utils/tab.component.ts
@@ -18,15 +18,8 @@ import {Component, Input} from '@angular/core';
 
 @Component({
     selector: 'app-tab',
-    styles: [`
-        .pane {
-            padding: 1em;
-        }
-    `],
-    template: `
-        <div [hidden]="!active" class="pane">
-        </div>
-    `
+    templateUrl: './tab.component.html',
+    styleUrls: ['./tab.component.css']
 })
 export class TabComponent {
     @Input('tabTitle') title: string;
diff --git a/gateway-admin-ui/admin-ui/app/utils/tabs.component.html b/gateway-admin-ui/admin-ui/app/utils/tabs.component.html
new file mode 100644
index 000000000..fa8b00b01
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/app/utils/tabs.component.html
@@ -0,0 +1,7 @@
+<ul class="nav nav-tabs">
+    @for (tab of tabs; track tab.title) {
+        <li (click)="selectTab(tab)" [class.active]="tab.active">
+            <a>{{tab.title}}</a>
+        </li>
+    }
+</ul>
diff --git a/gateway-admin-ui/admin-ui/app/utils/tabs.component.ts b/gateway-admin-ui/admin-ui/app/utils/tabs.component.ts
index fbfb44728..fef064879 100644
--- a/gateway-admin-ui/admin-ui/app/utils/tabs.component.ts
+++ b/gateway-admin-ui/admin-ui/app/utils/tabs.component.ts
@@ -19,35 +19,22 @@ import {TabComponent} from './tab.component';
 
 @Component({
     selector: 'app-tabs',
-    template: `
-        <ul class="nav nav-tabs">
-            <li *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active">
-                <a>{{tab.title}}</a>
-            </li>
-        </ul>
-
-    `
+    templateUrl: './tabs.component.html'
 })
 export class TabsComponent implements AfterContentInit {
 
     @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
 
-    // contentChildren are set
     ngAfterContentInit() {
-        // get all active tabs
         let activeTabs = this.tabs.filter((tab) => tab.active);
 
-        // if there is no active tab set, activate the first
         if (activeTabs.length === 0) {
             this.selectTab(this.tabs.first);
         }
     }
 
     selectTab(tab: TabComponent) {
-        // deactivate all tabs
         this.tabs.toArray().forEach(t => t.active = false);
-
-        // activate the tab the user has clicked on.
         tab.active = true;
     }
 
diff --git a/gateway-admin-ui/admin-ui/app/utils/validation-utils.ts b/gateway-admin-ui/admin-ui/app/utils/validation-utils.ts
index a471a9cfd..59ba3daf2 100644
--- a/gateway-admin-ui/admin-ui/app/utils/validation-utils.ts
+++ b/gateway-admin-ui/admin-ui/app/utils/validation-utils.ts
@@ -72,7 +72,7 @@ export class ValidationUtils {
         if (value) {
             try {
                 parsed = JSON.parse(value);
-            } catch (e) {
+            } catch (_e) {
                 // just return undefined
             }
         }
diff --git a/gateway-admin-ui/admin-ui/app/utils/xml.pipe.ts b/gateway-admin-ui/admin-ui/app/utils/xml.pipe.ts
index 5a82b2ce6..977c3a9e1 100644
--- a/gateway-admin-ui/admin-ui/app/utils/xml.pipe.ts
+++ b/gateway-admin-ui/admin-ui/app/utils/xml.pipe.ts
@@ -18,7 +18,9 @@ import {Pipe, PipeTransform} from '@angular/core';
 
 declare let vkbeautify: any;
 
-@Pipe({name: 'xml'})
+@Pipe({
+    name: 'xml'
+})
 export class XmlPipe implements PipeTransform {
 
     transform(value: string): string {
diff --git a/gateway-admin-ui/admin-ui/assets/sticky-footer.css b/gateway-admin-ui/admin-ui/assets/sticky-footer.css
deleted file mode 100644
index 01eb6d403..000000000
--- a/gateway-admin-ui/admin-ui/assets/sticky-footer.css
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Sticky footer styles
--------------------------------------------------- */
-html {
-    position: relative;
-    min-height: 100%;
-}
-
-body {
-    /* Margin bottom by footer height */
-    margin-bottom: 60px;
-}
-
-.footer {
-    position: absolute;
-    bottom: 0;
-    width: 100%;
-    /* Set the fixed height of the footer here */
-    height: 60px;
-    background-color: #f5f5f5;
-}
-
-.jumbotron {
-    padding: 0.5em 0.6em;
-}
diff --git a/gateway-admin-ui/admin-ui/index.html b/gateway-admin-ui/admin-ui/index.html
index ab2d882cf..ca83a15fb 100644
--- a/gateway-admin-ui/admin-ui/index.html
+++ b/gateway-admin-ui/admin-ui/index.html
@@ -19,46 +19,38 @@
     <title>Apache Knox Manager</title>
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <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 src="assets/vkbeautify.js"></script>
+    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
 </head>
 <body>
-<div class="navbar-wrapper">
-    <div class="container-fluid">
+    <nav class="navbar navbar-expand-lg navbar-dark bg-dark pt-0 top-navbar">
+        <div class="container-fluid align-items-start">
+            <a class="navbar-brand pt-1 pb-0 ps-0 pe-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>
+
+            <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>
 
-        <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 Manager"> </a>
+            <div class="collapse navbar-collapse" id="navbarContent">
+                <div class="ms-auto d-flex flex-column pt-0">
+                    <app-session-information></app-session-information>
                 </div>
-                <app-session-information></app-session-information>
             </div>
-        </nav>
-
-    </div>
+        </div>
+    </nav>
 
     <!-- Content -->
     <app-resource-management></app-resource-management>
 
-    <div class="container-fluid" style="padding-top: 50px !important">
-    </div>
-
     <footer class="footer">
         <div class="container-fluid">
-            <div>Knox Manager
-                <app-gateway-version></app-gateway-version>
-            </div>
+            <span class="footer-title">Knox Manager</span>
+            <app-gateway-version></app-gateway-version>
         </div>
     </footer>
 </body>
diff --git a/gateway-admin-ui/admin-ui/main.ts b/gateway-admin-ui/admin-ui/main.ts
index 3ed873ee4..e00dad4c2 100644
--- a/gateway-admin-ui/admin-ui/main.ts
+++ b/gateway-admin-ui/admin-ui/main.ts
@@ -16,13 +16,29 @@
  */
 import './polyfills.ts';
 
-import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
-import {enableProdMode} from '@angular/core';
-import {environment} from './environments/environment';
-import {AppModule} from './app/';
+import { enableProdMode, provideZoneChangeDetection } from '@angular/core';
+import { environment } from './environments/environment';
+import { bootstrapApplication } from '@angular/platform-browser';
+import { provideHttpClient } from '@angular/common/http';
+import { SessionInformationComponent } from './app/session-information/session.information.component';
+import { GatewayVersionComponent } from './app/gateway-version/gateway-version.component';
+import { AppComponent } from './app/app.component';
 
 if (environment.production) {
-    enableProdMode();
+  enableProdMode();
 }
 
-platformBrowserDynamic().bootstrapModule(AppModule);
+const bootstrapComponents = [
+  SessionInformationComponent,
+  GatewayVersionComponent,
+  AppComponent
+];
+
+bootstrapComponents.forEach(component => {
+  bootstrapApplication(component, {
+    providers: [
+      provideZoneChangeDetection(),
+      provideHttpClient()
+    ]
+  }).catch(err => console.error(err));
+});
diff --git a/gateway-admin-ui/admin-ui/app/service-definition/rewrite.rule.ts b/gateway-admin-ui/admin-ui/material-theme.scss
similarity index 86%
rename from gateway-admin-ui/admin-ui/app/service-definition/rewrite.rule.ts
rename to gateway-admin-ui/admin-ui/material-theme.scss
index 2f1e7014b..382c0c98a 100644
--- a/gateway-admin-ui/admin-ui/app/service-definition/rewrite.rule.ts
+++ b/gateway-admin-ui/admin-ui/material-theme.scss
@@ -14,9 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@use '@angular/material' as mat;
 
-export class RewriteRule {
-    dir: string;
-    name: string;
-    pattern: string;
+:root {
+  @include mat.theme((
+    color: (
+      primary: mat.$green-palette
+    )
+  ));
 }
diff --git a/gateway-admin-ui/admin-ui/polyfills.ts b/gateway-admin-ui/admin-ui/polyfills.ts
index 78cba4747..aebd58c4e 100644
--- a/gateway-admin-ui/admin-ui/polyfills.ts
+++ b/gateway-admin-ui/admin-ui/polyfills.ts
@@ -14,22 +14,4 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// 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/gateway-admin-ui/admin-ui/styles.css b/gateway-admin-ui/admin-ui/styles.css
deleted file mode 100644
index a7ad3e612..000000000
--- a/gateway-admin-ui/admin-ui/styles.css
+++ /dev/null
@@ -1,9 +0,0 @@
-/* You can add global styles to this file, and also import other style files */
-
-.navbar-static-top {
-    min-height: 110px;
-}
-
-.clickable {
-    cursor: pointer;
-}
diff --git a/gateway-admin-ui/admin-ui/styles.scss b/gateway-admin-ui/admin-ui/styles.scss
new file mode 100644
index 000000000..e48e1dea2
--- /dev/null
+++ b/gateway-admin-ui/admin-ui/styles.scss
@@ -0,0 +1,354 @@
+/*
+ * 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.
+ */
+
+/* Global styles */
+
+// Green theme palette
+$primary: #4caf50;
+$primary-dark: #388e3c;
+$primary-light: #c8e6c9;
+$primary-lighter: #e8f5e9;
+$primary-lightest: #f1f8e9;
+
+$accent: #ff9800;
+$warn: #f44336;
+
+$text-primary: #333;
+$text-secondary: #555;
+$text-muted: #999;
+$border-color: #ddd;
+$border-light: #eee;
+
+.top-navbar {
+    min-height: 5vw;
+}
+
+.clickable {
+    cursor: pointer;
+}
+
+body {
+    margin: 0;
+    padding-bottom: 40px;
+    font-size: 14px;
+}
+
+// Form input styling
+input[type="text"],
+input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="submit"]):not([type="button"]) {
+    padding: 3px 8px;
+    border: 1px solid $border-color;
+    border-radius: 4px;
+    font-size: 13px;
+    color: $text-primary;
+    background-color: #fff;
+    transition: border-color 0.15s, box-shadow 0.15s;
+    margin-right: 6px;
+
+    &:focus {
+        outline: none;
+        border-color: $primary;
+        box-shadow: 0 0 0 2px rgba($primary, 0.15);
+    }
+
+    &::placeholder {
+        color: $text-muted;
+    }
+}
+
+input[type="checkbox"] {
+    accent-color: $primary;
+    cursor: pointer;
+}
+
+.footer {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 40px;
+    line-height: 40px;
+    background-color: #f5f5f5;
+    border-top: 1px solid #e0e0e0;
+    z-index: 1000;
+}
+
+.footer .container-fluid {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    height: 100%;
+}
+
+.footer-title {
+    font-size: 12px;
+    font-weight: 600;
+    color: $text-primary;
+}
+
+
+a {
+    color: $primary-dark;
+    text-decoration: none;
+}
+
+// Bootstrap 5 compatibility for BS3 classes
+.btn-default {
+    color: $text-primary;
+    background-color: #fff;
+    border-color: #ccc;
+}
+
+.btn-default:hover {
+    color: $text-primary;
+    background-color: #e6e6e6;
+    border-color: #adadad;
+}
+
+.btn-primary {
+    color: #fff;
+    background-color: $primary;
+    border-color: $primary;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active {
+    color: #fff;
+    background-color: $primary-dark;
+    border-color: $primary-dark;
+}
+
+.btn-primary:disabled {
+    background-color: $primary-light;
+    border-color: $primary-light;
+}
+
+// Select / form-control styling
+select.form-control {
+    padding: 3px 8px;
+    border: 1px solid $border-color;
+    border-radius: 4px;
+    font-size: 13px;
+    color: $text-primary;
+    background-color: #fff;
+
+    &:focus {
+        outline: none;
+        border-color: $primary;
+        box-shadow: 0 0 0 2px rgba($primary, 0.15);
+    }
+}
+
+.pull-left {
+    float: left !important;
+}
+
+.pull-right {
+    float: right !important;
+}
+
+// Panel styling (BS3 compat)
+.panel {
+    border: 1px solid $border-color;
+    border-radius: 4px;
+    margin-bottom: 15px;
+    background-color: #fff;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+}
+
+.panel-default > .panel-heading {
+    color: $text-primary;
+    background-color: #f5f5f5;
+    border-bottom: 1px solid $border-color;
+    padding: 10px 15px;
+}
+
+.panel-body {
+    padding: 15px;
+}
+
+.panel-title {
+    margin: 0;
+    font-size: 15px;
+    font-weight: 500;
+}
+
+// Modal styling
+.modal {
+    z-index: 1050;
+}
+
+.modal-backdrop {
+    z-index: 1040;
+}
+
+.modal-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 16px;
+    border-bottom: 1px solid #dee2e6;
+    background-color: #f8f9fa;
+}
+
+.modal-title {
+    font-weight: 500;
+    color: $text-primary;
+}
+
+.modal-footer {
+    padding: 12px 16px;
+    border-top: 1px solid #dee2e6;
+    background-color: #f8f9fa;
+}
+
+// Icon button alignment
+.btn-xs {
+    padding: 2px 5px;
+    line-height: 1;
+    vertical-align: middle;
+    display: inline-block;
+}
+
+.btn-xs .material-icons {
+    vertical-align: middle;
+    line-height: 1;
+    display: inline-block;
+}
+
+.inline-glyph {
+    display: inline-block;
+    vertical-align: middle;
+    line-height: 1;
+}
+
+// Material Icons base
+.material-icons {
+    font-size: 16px;
+    vertical-align: middle;
+    line-height: 1;
+    color: $text-secondary;
+    transition: color 0.15s;
+}
+
+// Expand/collapse toggle icons
+span.clickable.material-icons,
+span.clickable.inline-glyph.material-icons {
+    font-size: 18px;
+    color: #666;
+}
+
+span.clickable.material-icons:hover,
+span.clickable.inline-glyph.material-icons:hover {
+    color: $primary;
+}
+
+// Icons inside buttons
+.btn .material-icons {
+    font-size: 14px;
+    color: inherit;
+}
+
+// Inline action icon buttons
+.inline-glyph .material-icons {
+    color: #666;
+}
+
+.inline-glyph:hover .material-icons {
+    color: $primary;
+}
+
+// Add action
+[title*="Add"] .material-icons,
+[title*="Create"] .material-icons {
+    color: $primary;
+}
+
+[title*="Add"]:hover .material-icons,
+[title*="Create"]:hover .material-icons {
+    color: $primary-dark;
+}
+
+// Remove/delete action
+[title*="Remove"] .material-icons,
+[title*="Delete"] .material-icons {
+    color: $text-muted;
+}
+
+[title*="Remove"]:hover .material-icons,
+[title*="Delete"]:hover .material-icons {
+    color: $warn;
+}
+
+// Discard/refresh
+button[title*="Discard"] .material-icons {
+    color: $accent;
+}
+
+button[title*="Discard"]:hover .material-icons {
+    color: #e68900;
+}
+
+// Save/update
+button[title*="Update"] .material-icons,
+button[title*="Save"] .material-icons {
+    color: $primary;
+}
+
+button[title*="Update"]:hover .material-icons,
+button[title*="Save"]:hover .material-icons {
+    color: $primary-dark;
+}
+
+// Validation error messages
+.validation-error {
+    display: inline-block;
+    margin-top: 2px;
+    margin-left: 4px;
+    padding: 2px 8px;
+    font-size: 12px;
+    color: $warn;
+    background-color: #fff5f5;
+    border: 1px solid rgba($warn, 0.3);
+    border-radius: 3px;
+}
+
+.required-marker {
+    color: $warn;
+    margin-left: 2px;
+}
+
+.required-footnote {
+    font-size: 12px;
+    color: $text-muted;
+    font-style: italic;
+}
+
+.required-footnote::before {
+    content: "* ";
+    color: $warn;
+}
+
+// Material table/paginator overrides
+.mat-mdc-table {
+    background-color: white !important;
+}
+
+.mat-mdc-paginator {
+    background-color: white !important;
+}
diff --git a/gateway-admin-ui/admin-ui/tsconfig.json b/gateway-admin-ui/admin-ui/tsconfig.json
index c56d60198..268b33345 100644
--- a/gateway-admin-ui/admin-ui/tsconfig.json
+++ b/gateway-admin-ui/admin-ui/tsconfig.json
@@ -4,16 +4,11 @@
     "declaration": false,
     "emitDecoratorMetadata": true,
     "experimentalDecorators": true,
-    "lib": [
-      "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/gateway-admin-ui/admin-ui/typings.d.ts b/gateway-admin-ui/admin-ui/typings.d.ts
deleted file mode 100644
index 32bf19d41..000000000
--- a/gateway-admin-ui/admin-ui/typings.d.ts
+++ /dev/null
@@ -1,18 +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.
- */
-// Typings reference file, you can add your own global typings here
-// https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
diff --git a/gateway-admin-ui/angular.json b/gateway-admin-ui/angular.json
index 72f6510b7..c2cdfd1aa 100644
--- a/gateway-admin-ui/angular.json
+++ b/gateway-admin-ui/angular.json
@@ -36,12 +36,14 @@
       "prefix": "app",
       "architect": {
         "build": {
-          "builder": "@angular-devkit/build-angular:browser",
+          "builder": "@angular/build:application",
           "options": {
-            "outputPath": "target/classes/admin-ui/app",
+            "outputPath": {
+              "base": "target/classes/admin-ui/app",
+              "browser": ""
+            },
             "index": "admin-ui/index.html",
-            "main": "admin-ui/main.ts",
-            "polyfills": "admin-ui/polyfills.ts",
+            "polyfills": ["admin-ui/polyfills.ts"],
             "tsConfig": "admin-ui/tsconfig.json",
             "assets": [
               "admin-ui/favicon.ico",
@@ -49,18 +51,16 @@
             ],
             "styles": [
               "node_modules/bootstrap/dist/css/bootstrap.min.css",
-              "admin-ui/styles.css"
+              "admin-ui/material-theme.scss",
+              "admin-ui/styles.scss"
             ],
             "scripts": [
-              "node_modules/jquery/dist/jquery.min.js",
-              "node_modules/bootstrap/dist/js/bootstrap.js",
-              "node_modules/ace-builds/src-min/ace.js",
-              "node_modules/ace-builds/src-min/theme-monokai.js"              
-            ]
+              "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
+            ],
+            "browser": "admin-ui/main.ts"
           },
           "configurations": {
             "production": {
-              "buildOptimizer": false,
               "aot": false,
               "fileReplacements": [
                 {
@@ -71,10 +71,8 @@
               "outputHashing": "all"
             },
             "development": {
-              "buildOptimizer": false,
               "aot": false,
               "optimization": false,
-              "vendorChunk": true,
               "extractLicenses": false,
               "sourceMap": true,
               "namedChunks": true
@@ -83,25 +81,21 @@
           "defaultConfiguration": "production"
         },
         "serve": {
-          "builder": "@angular-devkit/build-angular:dev-server",
+          "builder": "@angular/build:dev-server",
           "configurations": {
             "production": {
-              "browserTarget": "admin-ui:build:production"
+              "buildTarget": "admin-ui:build:production"
             },
             "development": {
-              "browserTarget": "admin-ui:build:development"
+              "buildTarget": "admin-ui:build:development"
             }
           },
-          "options": {
-            "browserTarget": "admin-ui:build",
-            "proxyConfig": "proxy.conf.json"
-          },
           "defaultConfiguration": "development"
         },
         "extract-i18n": {
-          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "builder": "@angular/build:extract-i18n",
           "options": {
-            "browserTarget": "admin-ui:build"
+            "buildTarget": "admin-ui:build"
           }
         },
         "lint": {
@@ -115,12 +109,5 @@
         }
       }
     }
-  },
-  "defaultProject": "admin-ui",
-  "cli": {
-    "schematicCollections": [
-      "@angular-eslint/schematics",
-      "@angular-eslint/schematics"
-    ]
   }
 }
diff --git a/gateway-admin-ui/eslint.config.js b/gateway-admin-ui/eslint.config.js
new file mode 100644
index 000000000..12b8d3c08
--- /dev/null
+++ b/gateway-admin-ui/eslint.config.js
@@ -0,0 +1,76 @@
+import js from "@eslint/js";
+import tseslint from "typescript-eslint";
+import angular from "@angular-eslint/eslint-plugin";
+import tsParser from "@typescript-eslint/parser";
+
+export default [
+    {
+        ignores: ["**/dist/**", "**/node_modules/**"],
+    },
+
+    js.configs.recommended,
+
+    {
+        files: ["**/*.ts"],
+        languageOptions: {
+            parser: tsParser,
+            parserOptions: {
+                project: ["./admin-ui/tsconfig.json"],
+                tsconfigRootDir: import.meta.dirname,
+            },
+            globals: {
+                console: "readonly",
+                window: "readonly",
+                document: "readonly",
+                setTimeout: "readonly",
+                atob: "readonly",
+                require: "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-vars": ["error", {argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_"}],
+            "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",
+        },
+    },
+];
\ No newline at end of file
diff --git a/gateway-admin-ui/package.json b/gateway-admin-ui/package.json
index 5f74ec23e..08f86584b 100644
--- a/gateway-admin-ui/package.json
+++ b/gateway-admin-ui/package.json
@@ -2,57 +2,44 @@
   "name": "ng-knox-ui",
   "version": "1.0.0",
   "license": "Apache-2.0",
+  "type": "module",
   "scripts": {
-    "start": "ng serve",
+    "start": "ng serve --verbose=true",
     "build": "ng build",
     "build-prod": "ng build --configuration production",
     "lint": "eslint admin-ui"
   },
   "private": true,
   "dependencies": {
-    "@angular/common": "^13.0.1",
-    "@angular/compiler": "^13.0.1",
-    "@angular/core": "^13.0.1",
-    "@angular/forms": "^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.4",
-    "jquery": "^3.5.1",
-    "js-yaml": "^3.13.1",
-    "ng2-ace-editor": "0.3.7",
-    "ace-builds": "^1.4.12",
-    "ng2-bs3-modal": "^0.15.0",
-    "ng2-validation": "^4.2.0",
-    "popper.js": "^1.14.7",
-    "rest-url-builder": "^1.0.6",
-    "rxjs": "^6.6.7",
-    "rxjs-compat": "^6.6.7",
-    "sweetalert2": "^11.6.5",
-    "ts-helpers": "^1.1.1",
-    "vkbeautify": "^0.99.3",
-    "zone.js": "~0.11.4"
+    "@angular/cdk": "^21.2.14",
+    "@angular/common": "^21.0.5",
+    "@angular/compiler": "^21.0.5",
+    "@angular/core": "^21.0.5",
+    "@angular/forms": "^21.0.5",
+    "@angular/material": "^21.2.14",
+    "@angular/platform-browser": "^21.0.5",
+    "@angular/platform-browser-dynamic": "^21.0.5",
+    "@angular/router": "^21.0.5",
+    "bootstrap": "^5.3.8",
+    "core-js": "^3.46.0",
+    "sweetalert2": "^11.26.3",
+    "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",
-    "@types/jasminewd2": "~2.0.2",
-    "@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"
+    "@angular-eslint/builder": "21.1.0",
+    "@angular-eslint/eslint-plugin": "21.1.0",
+    "@angular-eslint/eslint-plugin-template": "21.1.0",
+    "@angular-eslint/schematics": "21.1.0",
+    "@angular-eslint/template-parser": "21.1.0",
+    "@angular/build": "^21.0.3",
+    "@angular/cli": "^21.0.3",
+    "@angular/compiler-cli": "^21.0.5",
+    "@angular/language-service": "^21.0.5",
+    "@eslint/js": "^9.39.0",
+    "@types/node": "^24.5.2",
+    "eslint": "^9.39.0",
+    "ts-node": "~10.9.0",
+    "typescript": "~5.9.3",
+    "typescript-eslint": "^8.46.2"
   }
-}
+}
\ No newline at end of file
diff --git a/gateway-admin-ui/pom.xml b/gateway-admin-ui/pom.xml
index ff41c08de..6a435c458 100644
--- a/gateway-admin-ui/pom.xml
+++ b/gateway-admin-ui/pom.xml
@@ -37,7 +37,11 @@
                 <configuration>
                     <filesets>
                         <fileset>
-                            <directory>node_modules</directory>
+                            <directory>${project.basedir}</directory>
+                            <includes>
+                                <include>package-lock.json</include>
+                                <include>node_modules/**</include>
+                            </includes>
                         </fileset>
                     </filesets>
                 </configuration>
@@ -65,7 +69,7 @@
                             <goal>npm</goal>
                         </goals>
                         <configuration>
-                            <arguments>install --legacy-peer-deps</arguments>
+                            <arguments>install</arguments>
                         </configuration>
                     </execution>
                     <execution>
diff --git a/knox-token-generation-ui/token-generation/app/sessionInformation/session.information.component.html b/knox-token-generation-ui/token-generation/app/sessionInformation/session.information.component.html
index 7250e0166..719af147b 100644
--- a/knox-token-generation-ui/token-generation/app/sessionInformation/session.information.component.html
+++ b/knox-token-generation-ui/token-generation/app/sessionInformation/session.information.component.html
@@ -12,6 +12,5 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!-- The text color is the same as the Cloudera logo's color -->
 <div style="text-align: right; color: rgb(130, 180, 93);">Logged in as {{ getUser() }}</div>
 <div style="margin-left: 15%; text-align: center; color: rgb(130,180, 93); max-width: 70%; word-wrap: break-word;" [innerHTML]="getBannerText() | safeHtml"></div>
diff --git a/knox-token-management-ui/token-management/app/sessioninformation/session.information.component.html b/knox-token-management-ui/token-management/app/sessioninformation/session.information.component.html
index 30a7de284..9dd131f11 100644
--- a/knox-token-management-ui/token-management/app/sessioninformation/session.information.component.html
+++ b/knox-token-management-ui/token-management/app/sessioninformation/session.information.component.html
@@ -12,7 +12,6 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<!-- The text color is the same as the Cloudera logo's color -->
 <div style="text-align: right; color: rgb(130, 180, 93);">Logged in as {{ getUser() }}</div>
 <div style="margin-left: 15%; text-align: center; color: rgb(130,180, 93); max-width: 70%; word-wrap: break-word;" [innerHTML]="getBannerText() | safeHtml"></div>