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