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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5a70e628a KNOX-2859 - Token Management UI improvements (#713)
5a70e628a is described below

commit 5a70e628a74de26cf4b2370443777cd58abe64bf
Author: Sandor Molnar <[email protected]>
AuthorDate: Tue Jan 10 16:24:59 2023 +0100

    KNOX-2859 - Token Management UI improvements (#713)
    
    Replaced the old Angular2 Datatable with a more modern Material Table 
implementation and configured filtering, sorting and pagination on both tables 
on the Token Management UI.
---
 knox-token-management-ui/package-lock.json         |  11 +-
 knox-token-management-ui/package.json              |   1 -
 .../token-management/app/app.module.ts             |  20 ++-
 .../token-management/app/knox.token.ts             |   2 +
 .../app/token.management.component.html            | 176 ++++++++++++---------
 .../app/token.management.component.ts              | 100 +++++++++++-
 .../{styles.css => assets/token-management-ui.css} |  23 ++-
 .../token-management/index.html                    |   1 +
 .../token-management/styles.css                    |   2 +
 9 files changed, 238 insertions(+), 98 deletions(-)

diff --git a/knox-token-management-ui/package-lock.json 
b/knox-token-management-ui/package-lock.json
index eaebe3a1c..2701270d0 100644
--- a/knox-token-management-ui/package-lock.json
+++ b/knox-token-management-ui/package-lock.json
@@ -2967,14 +2967,6 @@
       "integrity": 
"sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
       "dev": true
     },
-    "angular2-datatable": {
-      "version": "0.6.0",
-      "resolved": 
"https://registry.npmjs.org/angular2-datatable/-/angular2-datatable-0.6.0.tgz";,
-      "integrity": 
"sha512-Fgg3hg3Pyg80Tp21Fu9qzsj9yx4941cIbbWpbKHJlQua5eketQPAp+yEbnz0KnaMprlpQg5IBe6xdIAg1G6aCQ==",
-      "requires": {
-        "lodash": "^4.0.0"
-      }
-    },
     "ansi-colors": {
       "version": "4.1.1",
       "resolved": 
"https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz";,
@@ -6299,7 +6291,8 @@
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz";,
-      "integrity": 
"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+      "integrity": 
"sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true
     },
     "lodash.debounce": {
       "version": "4.0.8",
diff --git a/knox-token-management-ui/package.json 
b/knox-token-management-ui/package.json
index 26d2d1d58..0864440d7 100644
--- a/knox-token-management-ui/package.json
+++ b/knox-token-management-ui/package.json
@@ -21,7 +21,6 @@
     "@angular/platform-browser": "^13.0.1",
     "@angular/platform-browser-dynamic": "^13.0.1",
     "@angular/router": "^13.0.1",
-    "angular2-datatable": "^0.6.0",
     "bootstrap": "^3.4.1",
     "core-js": "^2.6.11",
     "jquery": "^3.5.1",
diff --git a/knox-token-management-ui/token-management/app/app.module.ts 
b/knox-token-management-ui/token-management/app/app.module.ts
index 769f92406..8ef4ab433 100644
--- a/knox-token-management-ui/token-management/app/app.module.ts
+++ b/knox-token-management-ui/token-management/app/app.module.ts
@@ -19,18 +19,34 @@ import {BrowserModule} from '@angular/platform-browser';
 import {HttpClientModule, HttpClientXsrfModule} from '@angular/common/http';
 import {MatGridListModule} from '@angular/material/grid-list';
 import {BsModalModule} from 'ng2-bs3-modal';
-import {DataTableModule} from 'angular2-datatable';
+import {MatTableModule, MatTableDataSource} from '@angular/material/table';
+import {MatSortModule} from '@angular/material/sort';
+import {MatPaginatorModule} from '@angular/material/paginator';
+import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
+import {MatInputModule} from '@angular/material/input';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+
 
 import {TokenManagementComponent} from './token.management.component';
 import {TokenManagementService} from './token.management.service';
 
 @NgModule({
     imports: [BrowserModule,
+        BrowserAnimationsModule,
         HttpClientModule,
         HttpClientXsrfModule,
         MatGridListModule,
         BsModalModule,
-        DataTableModule
+        FormsModule,
+        ReactiveFormsModule
+    ],
+    exports: [MatTableModule,
+        MatTableDataSource,
+        MatSortModule,
+        MatPaginatorModule,
+        MatProgressSpinnerModule,
+        MatInputModule
     ],
     declarations: [TokenManagementComponent],
     providers: [TokenManagementService],
diff --git a/knox-token-management-ui/token-management/app/knox.token.ts 
b/knox-token-management-ui/token-management/app/knox.token.ts
index d2587bf68..c21e979ef 100644
--- a/knox-token-management-ui/token-management/app/knox.token.ts
+++ b/knox-token-management-ui/token-management/app/knox.token.ts
@@ -20,7 +20,9 @@ import {Metadata} from './metadata';
 export class KnoxToken {
     tokenId: string;
     issueTime: string;
+    issueTimeLong: number;
     expiration: string;
+    expirationLong: number;
     maxLifetime: string;
     metadata: Metadata;
 }
diff --git 
a/knox-token-management-ui/token-management/app/token.management.component.html 
b/knox-token-management-ui/token-management/app/token.management.component.html
index 5a9157565..427e603e5 100644
--- 
a/knox-token-management-ui/token-management/app/token.management.component.html
+++ 
b/knox-token-management-ui/token-management/app/token.management.component.html
@@ -20,90 +20,116 @@
             <span class="glyphicon glyphicon-refresh"></span>
         </button>
     </div>
+
     <div class="table-responsive" style="width:100%; overflow: auto; 
overflow-y: scroll; padding: 10px 0px 0px 0px;">
         <label>My Knox Tokens</label>
-        <table class="table table-hover" [mfData]="knoxTokens" 
#tokens="mfDataTable" [mfRowsOnPage]="10">
-            <thead>
-            <tr>
-                <th>Token ID</th>
-                <th>Issued</th>
-                <th>Expires</th>
-                <th>Comment</th>
-                <th>Additional Metadata</th>
-                <th>Actions</th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr *ngFor="let knoxToken of tokens.data">
-                <td>{{knoxToken.tokenId}}</td>
-                <td>{{formatDateTime(knoxToken.issueTimeLong)}}</td>
-                <td *ngIf="!isTokenExpired(knoxToken.expirationLong)" 
style="color: green">{{formatDateTime(knoxToken.expirationLong)}}</td>
-                <td *ngIf="isTokenExpired(knoxToken.expirationLong)" 
style="color: red">{{formatDateTime(knoxToken.expirationLong)}}</td>
-                <td>{{knoxToken.metadata.comment}}</td>
-                <td>
-                  <ul>
-                    <li *ngFor="let metadata of 
getCustomMetadataArray(knoxToken)">
-                      {{metadata[0]}} = {{metadata[1]}}
-                    </li>
-                  </ul>
-                </td>
-                <td>
+
+        <mat-form-field [floatLabel]="always" appearance="fill" #search>
+            <mat-label>Search by Token ID, Comment or Metadata...</mat-label>
+            <input matInput (keyup)="applyFilter(false, $event.target.value)">
+        </mat-form-field>
+
+        <mat-table [dataSource]="knoxTokens" matSort #ownSort="matSort">
+            <ng-container matColumnDef="tokenId">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="tokenId">Token ID</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{knoxToken.tokenId}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="issued">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="issueTime">Issued</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{formatDateTime(knoxToken.issueTimeLong)}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="expires">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="expiration">Expires</mat-header-cell>
+                <mat-cell *matCellDef="let knoxToken" 
[style.color]="getExpirationColor(knoxToken.expirationLong)">{{formatDateTime(knoxToken.expirationLong)}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="comment">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="metadata.comment">Comment</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{knoxToken.metadata.comment}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="metadata">
+                <mat-header-cell *matHeaderCellDef>Additional 
Metadata</mat-header-cell>
+                <mat-cell *matCellDef="let knoxToken">
+                   <ul>
+                     <li *ngFor="let metadata of 
getCustomMetadataArray(knoxToken)">
+                       {{metadata[0]}} = {{metadata[1]}}
+                     </li>
+                   </ul>
+                </mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="actions">
+                <mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
+                <mat-cell *matCellDef="let knoxToken">
                     <button *ngIf="knoxToken.metadata.enabled && 
!isTokenExpired(knoxToken.expirationLong)" 
(click)="disableToken(knoxToken.tokenId);">Disable</button>
                     <button *ngIf="!knoxToken.metadata.enabled && 
!isTokenExpired(knoxToken.expirationLong)" 
(click)="enableToken(knoxToken.tokenId);">Enable</button>
                     <button 
(click)="revokeToken(knoxToken.tokenId);">Revoke</button>
-                </td>
-            </tr>
-            </tbody>
-                   <tfoot>
-                   <tr>
-                       <td colspan="6">
-                           <mfBootstrapPaginator 
[rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
-                       </td>
-                   </tr>
-                   </tfoot>
-        </table>
+                </mat-cell>
+            </ng-container>
+
+            <mat-header-row 
*matHeaderRowDef="displayedColumns"></mat-header-row>
+            <mat-row *matRowDef="let row; columns: 
displayedColumns;"></mat-row>
+
+        </mat-table>
+        <mat-paginator #ownPaginator [pageSizeOptions]="[5, 10, 25, 100]" 
[showFirstLastButtons]="true"></mat-paginator>
     </div>
 
+
     <!-- 'doAs' Knox Tokens (tokens created by the current user on behalf on 
another user -->
 
-    <div class="table-responsive" style="width:100%; overflow: auto; 
overflow-y: scroll; padding: 10px 0px 0px 0px;" 
*ngIf="isImpersonationEnabled()">
-        <label>Impersonation Knox Tokens</label>
-        <table class="table table-hover" [mfData]="doAsKnoxTokens" 
#doAsTokens="mfDataTable" [mfRowsOnPage]="10">
-            <thead>
-            <tr>
-                <th>Token ID</th>
-                <th>Issued</th>
-                <th>Expires</th>
-                <th>Comment</th>
-                <th>Additional Metadata</th>
-                <th>Impersonated User<th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr *ngFor="let doAsKnoxtoken of doAsTokens.data">
-                <td>{{doAsKnoxtoken.tokenId}}</td>
-                <td>{{formatDateTime(doAsKnoxtoken.issueTimeLong)}}</td>
-                <td *ngIf="!isTokenExpired(doAsKnoxtoken.expirationLong)" 
style="color: green">{{formatDateTime(doAsKnoxtoken.expirationLong)}}</td>
-                <td *ngIf="isTokenExpired(doAsKnoxtoken.expirationLong)" 
style="color: red">{{formatDateTime(doAsKnoxtoken.expirationLong)}}</td>
-                <td>{{doAsKnoxtoken.metadata.comment}}</td>
-                <td>
-                  <ul>
-                    <li *ngFor="let metadata of 
getCustomMetadataArray(doAsKnoxtoken)">
-                      {{metadata[0]}} = {{metadata[1]}}
-                    </li>
-                  </ul>
-                </td>
-                <td>{{doAsKnoxtoken.metadata.userName}}</td>
-            </tr>
-            </tbody>
-                   <tfoot>
-                   <tr>
-                       <td colspan="6">
-                           <mfBootstrapPaginator 
[rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
-                       </td>
-                   </tr>
-                   </tfoot>
-        </table>
+    <div class="table-responsive" style="width:100%; overflow: auto; 
overflow-y: scroll; padding: 10px 0px 0px 0px;">
+        <label>Impersonated Knox Tokens</label>
+
+        <mat-form-field [floatLabel]="always" appearance="fill" 
#impersonationSearch>
+            <mat-label>Search by Token ID, Comment, Metadata or Impersonated 
User Name...</mat-label>
+            <input matInput (keyup)="applyFilter(true, $event.target.value)">
+        </mat-form-field>
+
+        <mat-table [dataSource]="doAsKnoxTokens" matSort 
#impersonationSort="matSort">
+            <ng-container matColumnDef="impersonation.tokenId">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="impersonation.tokenId">Token ID</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{knoxToken.tokenId}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="impersonation.issued">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="impersonation.issueTime">Issued</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{formatDateTime(knoxToken.issueTimeLong)}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="impersonation.expires">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="impersonation.expiration">Expires</mat-header-cell>
+                <mat-cell *matCellDef="let knoxToken" 
[style.color]="getExpirationColor(knoxToken.expirationLong)">{{formatDateTime(knoxToken.expirationLong)}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="impersonation.comment">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="impersonation.metadata.comment">Comment</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{knoxToken.metadata.comment}}</mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="impersonation.metadata">
+                <mat-header-cell *matHeaderCellDef>Additional 
Metadata</mat-header-cell>
+                <mat-cell *matCellDef="let knoxToken">
+                   <ul>
+                     <li *ngFor="let metadata of 
getCustomMetadataArray(knoxToken)">
+                       {{metadata[0]}} = {{metadata[1]}}
+                     </li>
+                   </ul>
+                </mat-cell>
+            </ng-container>
+
+            <ng-container matColumnDef="impersonation.user.name">
+                <mat-header-cell *matHeaderCellDef 
mat-sort-header="impersonation.metadata.username">Impersonated 
User</mat-header-cell>
+                <mat-cell *matCellDef="let 
knoxToken">{{knoxToken.metadata.userName}}</mat-cell>
+            </ng-container>
+
+            <mat-header-row 
*matHeaderRowDef="impersonationDisplayedColumns"></mat-header-row>
+            <mat-row *matRowDef="let row; columns: 
impersonationDisplayedColumns"></mat-row>
+
+        </mat-table>
+        <mat-paginator #impersonationPaginator [pageSizeOptions]="[5, 10, 25, 
100]" [showFirstLastButtons]="true"></mat-paginator>
     </div>
 </div>
 
diff --git 
a/knox-token-management-ui/token-management/app/token.management.component.ts 
b/knox-token-management-ui/token-management/app/token.management.component.ts
index 17afa78ed..fe1a11c9d 100644
--- 
a/knox-token-management-ui/token-management/app/token.management.component.ts
+++ 
b/knox-token-management-ui/token-management/app/token.management.component.ts
@@ -14,13 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit} from '@angular/core';
+import {Component, OnInit, ViewChild} from '@angular/core';
 import {TokenManagementService} from './token.management.service';
 import {KnoxToken} from './knox.token';
+import {MatTableDataSource} from '@angular/material/table';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatSort} from '@angular/material/sort';
 
 @Component({
     selector: 'app-token-management',
     templateUrl: './token.management.component.html',
+    styleUrls: ['../assets/token-management-ui.css'],
     providers: [TokenManagementService]
 })
 
@@ -29,10 +33,19 @@ export class TokenManagementComponent implements OnInit {
     tokenGenerationPageURL = window.location.pathname.replace(new 
RegExp('token-management/.*'), 'token-generation/index.html');
 
     userName: string;
-    knoxTokens: KnoxToken[];
-    doAsKnoxTokens: KnoxToken[];
+    knoxTokens: MatTableDataSource<KnoxToken> = new MatTableDataSource();
+    doAsKnoxTokens: MatTableDataSource<KnoxToken> = new MatTableDataSource();
     impersonationEnabled: boolean;
 
+    displayedColumns = ['tokenId', 'issued', 'expires', 'comment', 'metadata', 
'actions'];
+    @ViewChild('ownPaginator') paginator: MatPaginator;
+    @ViewChild('ownSort') sort: MatSort = new MatSort();
+
+    impersonationDisplayedColumns = ['impersonation.tokenId', 
'impersonation.issued', 'impersonation.expires', 'impersonation.comment',
+                                     'impersonation.metadata', 
'impersonation.user.name'];
+    @ViewChild('impersonationPaginator') impersonationPaginator: MatPaginator;
+    @ViewChild('impersonationSort') impersonationSort: MatSort = new MatSort();
+
     toggleBoolean(propertyName: string) {
         this[propertyName] = !this[propertyName];
     }
@@ -42,6 +55,53 @@ export class TokenManagementComponent implements OnInit {
     }
 
     constructor(private tokenManagementService: TokenManagementService) {
+        let isMatch: (record: KnoxToken, filter: String, impersonated: 
boolean) => boolean = (record, filter, impersonated) => {
+          let normalizedFilter = filter.trim().toLocaleLowerCase();
+          let matchesTokenId = 
record.tokenId.toLocaleLowerCase().includes(normalizedFilter);
+          let matchesComment = record.metadata.comment && 
record.metadata.comment.toLocaleLowerCase().includes(normalizedFilter);
+          let matchesCustomMetadata = false;
+          if (record.metadata.customMetadataMap) {
+            for (let entry of 
Array.from(Object.entries(record.metadata.customMetadataMap))) {
+                 if (entry[0].toLocaleLowerCase().includes(normalizedFilter) 
|| entry[1].toLocaleLowerCase().includes(normalizedFilter)) {
+                  matchesCustomMetadata = true;
+                  break;
+              }
+            }
+          } else {
+            matchesCustomMetadata = true; // nothing to match
+          }
+
+          let matchesImpersonatedUserName = false;  // doAs username should be 
checked only if impersonation is enabled
+          if (impersonated) {
+              matchesImpersonatedUserName = 
record.metadata.userName.toLocaleLowerCase().includes(normalizedFilter);
+          }
+
+          return matchesTokenId || matchesComment || matchesCustomMetadata || 
matchesImpersonatedUserName;
+        };
+
+        this.knoxTokens.filterPredicate = function (record, filter) {
+             return isMatch(record, filter, false);
+        };
+
+        this.doAsKnoxTokens.filterPredicate = function (record, filter) {
+          return isMatch(record, filter, true);
+        };
+
+        this.knoxTokens.sortingDataAccessor = (item, property) => {
+           switch(property) {
+             case 'metadata.comment': return item.metadata.comment;
+             default: return item[property];
+           }
+        };
+
+        this.doAsKnoxTokens.sortingDataAccessor = (item, property) => {
+           let normalizedPropertyName = property.replace('impersonation.', '');
+           switch(normalizedPropertyName) {
+             case 'metadata.comment': return item.metadata.comment;
+             case 'metadata.username': return item.metadata.userName;
+             default: return item[normalizedPropertyName];
+           }
+        };
     }
 
     ngOnInit(): void {
@@ -57,13 +117,29 @@ export class TokenManagementComponent implements OnInit {
     }
 
     fetchAllKnoxTokens(): void {
-        this.fetchKnoxTokens(true);
         this.fetchKnoxTokens(false);
+        this.fetchKnoxTokens(true);
     }
 
     fetchKnoxTokens(impersonated: boolean): void {
         this.tokenManagementService.getKnoxTokens(this.userName, impersonated)
-            .then(tokens => impersonated ? this.doAsKnoxTokens = tokens : 
this.knoxTokens = tokens);
+            .then(tokens => this.populateTokens(impersonated, tokens));
+    }
+
+    populateTokens(impersonated: boolean, tokens: KnoxToken[]) {
+        if (impersonated) {
+            this.doAsKnoxTokens.data = tokens;
+            setTimeout(() => {
+                this.doAsKnoxTokens.paginator = this.impersonationPaginator;
+                this.doAsKnoxTokens.sort = this.impersonationSort;
+            });
+        } else {
+            this.knoxTokens.data = tokens;
+            setTimeout(() => {
+                this.knoxTokens.paginator = this.paginator;
+                this.knoxTokens.sort = this.sort;
+            });
+        }
     }
 
     disableToken(tokenId: string) {
@@ -90,6 +166,10 @@ export class TokenManagementComponent implements OnInit {
         return Date.now() > expiration;
     }
 
+    getExpirationColor(expiration: number): string {
+        return this.isTokenExpired(expiration) ? 'red' : 'green';
+    }
+
     isImpersonationEnabled(): boolean {
         return this.impersonationEnabled;
     }
@@ -102,4 +182,14 @@ export class TokenManagementComponent implements OnInit {
       return Array.from(Object.entries(mdMap));
     }
 
+    applyFilter(impersonated: boolean, filterValue: string) {
+        filterValue = filterValue.trim(); // Remove whitespace
+        filterValue = filterValue.toLowerCase(); // Datasource defaults to 
lowercase matches
+        if (impersonated) {
+            this.doAsKnoxTokens.filter = filterValue;
+        } else {
+            this.knoxTokens.filter = filterValue;
+        }
+    }
+
 }
diff --git a/knox-token-management-ui/token-management/styles.css 
b/knox-token-management-ui/token-management/assets/token-management-ui.css
similarity index 76%
copy from knox-token-management-ui/token-management/styles.css
copy to knox-token-management-ui/token-management/assets/token-management-ui.css
index 56888df02..565ae446f 100644
--- a/knox-token-management-ui/token-management/styles.css
+++ b/knox-token-management-ui/token-management/assets/token-management-ui.css
@@ -15,13 +15,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+ 
+.example-container {
+  display: flex;
+  flex-direction: column;
+  min-width: 300px;
+}
 
-/* You can add global styles to this file, and also import other style files */
-
-.navbar-static-top {
-    min-height: 110px;
+.example-header {
+  min-height: 64px;
+  padding: 8px 24px 0;
 }
 
-.clickable {
-    cursor: pointer;
+.mat-form-field {
+  font-size: 14px;
+  width: 100%;
 }
+
+.mat-table {
+  overflow: auto;
+  max-height: 500px;
+}
\ No newline at end of file
diff --git a/knox-token-management-ui/token-management/index.html 
b/knox-token-management-ui/token-management/index.html
index 9d7f52c41..f347e1afc 100644
--- a/knox-token-management-ui/token-management/index.html
+++ b/knox-token-management-ui/token-management/index.html
@@ -22,6 +22,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <!-- Custom styles for this template -->
     <link href="assets/sticky-footer.css" rel="stylesheet">
+    <link href="https://fonts.googleapis.com/icon?family=Material+Icons"; 
rel="stylesheet">
        <script>
          window['base-href'] = window.location.pathname;
          console.log(window['base-href']);
diff --git a/knox-token-management-ui/token-management/styles.css 
b/knox-token-management-ui/token-management/styles.css
index 56888df02..7240de9e3 100644
--- a/knox-token-management-ui/token-management/styles.css
+++ b/knox-token-management-ui/token-management/styles.css
@@ -18,6 +18,8 @@
 
 /* You can add global styles to this file, and also import other style files */
 
+@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
+
 .navbar-static-top {
     min-height: 110px;
 }

Reply via email to