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;
}