From c53b09ad8e77d977605e6c5c431f6f0550c4c0e7 Mon Sep 17 00:00:00 2001 From: thanicz Date: Thu, 25 Sep 2025 14:06:46 +0200 Subject: [PATCH 1/2] KNOX-3197: Upgrade homepage UI to node 22, angular 20+ ang bootstrap to 5+ --- knox-homepage-ui/.eslintrc.json | 98 ------ knox-homepage-ui/angular.json | 64 +++- knox-homepage-ui/eslint.config.js | 74 +++++ .../apiservice-dialog.component.css | 68 ++++ .../apiservice-dialog.component.html | 64 ++++ .../apiservice-dialog.component.ts | 37 +++ knox-homepage-ui/home/app/app.module.ts | 53 +-- .../general.proxy.information.component.html | 6 +- .../general.proxy.information.component.ts | 9 +- .../general.proxy.information.ts | 0 .../home/app/{topologies => model}/sample.ts | 0 .../home/app/{topologies => model}/service.ts | 0 .../session.information.ts | 0 .../topology.information.ts | 8 +- .../app/{ => service}/homepage.service.ts | 6 +- .../session.information.component.ts | 21 +- .../topology.information.component.css | 45 ++- .../topology.information.component.html | 308 +++++++----------- .../topology.information.component.ts | 184 ++++++++--- .../uiservice-dialog.component.css | 0 .../uiservice-dialog.component.html | 51 +++ .../uiservice-dialog.component.ts | 56 ++++ knox-homepage-ui/home/app/util/safehtml.ts | 27 ++ knox-homepage-ui/home/index.html | 48 ++- knox-homepage-ui/home/main.ts | 40 ++- knox-homepage-ui/home/polyfills.ts | 2 +- knox-homepage-ui/home/tsconfig.json | 2 +- knox-homepage-ui/package.json | 62 ++-- knox-homepage-ui/pom.xml | 2 +- knox-homepage-ui/tsconfig.json | 14 + pom.xml | 4 +- 31 files changed, 871 insertions(+), 482 deletions(-) delete mode 100644 knox-homepage-ui/.eslintrc.json create mode 100644 knox-homepage-ui/eslint.config.js create mode 100644 knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.css create mode 100644 knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.html create mode 100644 knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.ts rename knox-homepage-ui/home/app/{generalProxyInformation => model}/general.proxy.information.ts (100%) rename knox-homepage-ui/home/app/{topologies => model}/sample.ts (100%) rename knox-homepage-ui/home/app/{topologies => model}/service.ts (100%) rename knox-homepage-ui/home/app/{sessionInformation => model}/session.information.ts (100%) rename knox-homepage-ui/home/app/{topologies => model}/topology.information.ts (89%) rename knox-homepage-ui/home/app/{ => service}/homepage.service.ts (96%) create mode 100644 knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.css create mode 100644 knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.html create mode 100644 knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.ts create mode 100644 knox-homepage-ui/home/app/util/safehtml.ts create mode 100644 knox-homepage-ui/tsconfig.json diff --git a/knox-homepage-ui/.eslintrc.json b/knox-homepage-ui/.eslintrc.json deleted file mode 100644 index 165b72fdd5..0000000000 --- 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 2e90da6217..e957cf1e64 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": { @@ -110,11 +111,42 @@ } } }, - "defaultProject": "home", "cli": { "schematicCollections": [ "@angular-eslint/schematics", "@angular-eslint/schematics" ] + }, + "schematics": { + "@angular-eslint/schematics:application": { + "setParserOptionsProject": true + }, + "@angular-eslint/schematics:library": { + "setParserOptionsProject": true + }, + "@schematics/angular:component": { + "type": "component" + }, + "@schematics/angular:directive": { + "type": "directive" + }, + "@schematics/angular:service": { + "type": "service" + }, + "@schematics/angular:guard": { + "typeSeparator": "." + }, + "@schematics/angular:interceptor": { + "typeSeparator": "." + }, + "@schematics/angular:module": { + "typeSeparator": "." + }, + "@schematics/angular:pipe": { + "typeSeparator": "." + }, + "@schematics/angular:resolver": { + "typeSeparator": "." + } } } diff --git a/knox-homepage-ui/eslint.config.js b/knox-homepage-ui/eslint.config.js new file mode 100644 index 0000000000..ed79a134c4 --- /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 0000000000..1d21265a03 --- /dev/null +++ b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.css @@ -0,0 +1,68 @@ +.info-section { + padding: 16px 0; +} + +.info-row { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 16px; + margin-bottom: 16px; + align-items: start; +} + +.info-label { + padding: 8px 0; + color: #666; +} + +.info-value { + padding: 8px 0; + word-break: break-word; +} + +.small { + font-size: 0.875em; + color: #666; + margin-left: 4px; +} + +.samples-container { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sample-item { + background-color: #f5f5f5; + padding: 12px; + border-radius: 4px; + border-left: 4px solid #007bff; +} + +.sample-desc { + margin-bottom: 8px; + color: #007bff; +} + +.sample-value { + font-family: 'Courier New', monospace; + font-size: 0.9em; + background-color: white; + padding: 8px; + border-radius: 4px; + white-space: pre-wrap; + word-break: break-all; +} + +.no-samples { + color: #666; +} + +.no-samples a { + color: #007bff; + text-decoration: none; +} + +.no-samples a:hover { + text-decoration: underline; +} 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 0000000000..14b10d1c88 --- /dev/null +++ b/knox-homepage-ui/home/app/apiservice-dialog/apiservice-dialog.component.html @@ -0,0 +1,64 @@ + +

+ {{ data?.shortDesc }} +

+ + +
+
+
Knox Service Name
+
+ {{ data?.serviceName }} + (v{{ data?.version }}) +
+
+ +
+
Description
+
{{ data?.description }}
+
+ +
+
Sample(s)
+
+ +
+
+
{{ sample.description }}
+
{{ sample.value }}
+
+
+
+ + +
+

No samples found in service metadata.

+

+ Check the service documentation: + + {{ data.serviceUrls[0] }} + +

+
+
+
+
+
+
+ + + + 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 0000000000..3ad82fb6e0 --- /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 + ) { + } +} diff --git a/knox-homepage-ui/home/app/app.module.ts b/knox-homepage-ui/home/app/app.module.ts index b59429edca..9b22cf7355 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 8299e5ca1b..2f0a4e979b 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. -->
-

 General Proxy Information

+

+ remove + add + General Proxy Information +

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 3143ed4e12..efa6c087d2 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/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 dabd4c8e28..e2871f8fec 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 @@ export class TopologyInformation { topology: string; pinned: boolean; apiServicesViewVersion: string; - apiServices: Service[]; - uiServices: Service[]; + apiServices: { + service: Service[]; + }; + uiServices: { + service: Service[]; + }; } diff --git a/knox-homepage-ui/home/app/homepage.service.ts b/knox-homepage-ui/home/app/service/homepage.service.ts similarity index 96% rename from knox-homepage-ui/home/app/homepage.service.ts rename to knox-homepage-ui/home/app/service/homepage.service.ts index cbad3d5809..8232ef54b3 100644 --- a/knox-homepage-ui/home/app/homepage.service.ts +++ b/knox-homepage-ui/home/app/service/homepage.service.ts @@ -21,9 +21,9 @@ 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'; +import {GeneralProxyInformation} from '../model/general.proxy.information'; +import {TopologyInformation} from '../model/topology.information'; +import {SessionInformation} from '../model/session.information'; @Injectable() export class HomepageService { 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 0f7b63da97..3c148daed7 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 5d2f9cd2ea..720558760f 100644 --- a/knox-homepage-ui/home/app/topologies/topology.information.component.css +++ b/knox-homepage-ui/home/app/topologies/topology.information.component.css @@ -35,6 +35,45 @@ mat-grid-tile-footer figcaption h3 { visibility: visible; } -/deep/ .groupServiceInformationModal { - width: 80%; -} \ No newline at end of file +::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 .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); +} 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 510945441d..550dddb4ff 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,139 @@ See the License for the specific language governing permissions and limitations under the License. --> -
+
-

 Topologies

+

+ remove + addTopologies +

- -
- - {{topology.topology}} - - -
- -
- -
UI Services
- - - - - - -
- - - - + +
+ remove + add + {{topology.topology}} + + push_pin + settings +
+ +
+ +
UI Services
+ + + + + + +
+ + + + + {{service.shortDesc}} + + +

{{service.shortDesc}} (v{{service.version}})

+
+ +

{{service.description}}

+
+
+ + +
+ + +

{{service.shortDesc}} ({{service.serviceUrls.length}})

+
+
+ + + + + + +
API Services
+ +
+ + + +
No API services found
+
+ + +
+ + + + + + + + + + + + + + + + + +
Description +
+ info + {{ service.shortDesc }} + (v{{ service.version }}) +
+
URLs + + {{ url }} +
+
+
+ + + + +
+ + + + + + + {{service.shortDesc}} - + -

{{service.shortDesc}} (v{{service.version}})

+

{{service.shortDesc}} (v{{service.version}})

{{service.description}}

- - - -
- - -

{{service.shortDesc}} ({{service.serviceUrls.length}})

-
-
- -
-
- - - -
API Services
- - - - - - - - - - - - - - - - - - - - - -
No API services found
API services
- - {{service.shortDesc}} (v{{service.version}}) - - - {{serviceUrl}} -
-
-
- -
- - - - - - - - {{service.shortDesc}} - - -

{{service.shortDesc}} (v{{service.version}})

-
- -

{{service.description}}

-
-
-
- - -
+ + + + +
- - - - - - -
- - - - - - - - - - - - - - - - - -
Knox Service Name{{selectedApiService.serviceName}} (v{{selectedApiService.version}})
Description{{selectedApiService.description}}
Sample(s) - -
- -
- - - -
-
- - - - -
{{sample.description}}
  {{sample.value}}
There is no any sample found in service metadata
 You may check out the service's documentation and find out how to use its REST API. The service's URL is {{selectedApiService.serviceUrls[0]}}
-
-
- -
- - - -
- - - - - - - - - {{selectedGroupService.description}} - -

- - - - - - - - - - - {{selectedGroupService.shortDesc}} - - -

{{selectedGroupService.shortDesc}} {{getServiceUrlHostAndPort(serviceUrl)}}

-
-
-
-
- - - -
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 bbb18763b0..055a17dd63 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>(); + @ViewChildren(MatPaginator) allPaginators!: QueryList; + + 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(); + 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 { + return this.topologyDataSources.get(topology.topology); } toggleBoolean(propertyName: string) { @@ -59,10 +129,6 @@ export class TopologyInformationsComponent implements OnInit { 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,78 @@ export class TopologyInformationsComponent implements OnInit { 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: '600px', + data: service + }); + } + + openGroupServiceDialog(service: any): void { + this.dialog.open(UiserviceDialogComponent, { + width: '800px', + data: service + }); + } } 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 0000000000..e69de29bb2 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 0000000000..a98655f592 --- /dev/null +++ b/knox-homepage-ui/home/app/uiservice-dialog/uiservice-dialog.component.html @@ -0,0 +1,51 @@ + +

+ {{ data.shortDesc }} + (v{{ data.version }}) +

+ + +

{{ data.description }}

+ + + Search by hostname, port... + + + + + + + + + + + {{ data.shortDesc }} + + + +

{{ data.shortDesc }} {{ getServiceUrlHostAndPort(serviceUrl) }}

+
+
+
+
+ + + + 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 0000000000..88042c2154 --- /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/util/safehtml.ts b/knox-homepage-ui/home/app/util/safehtml.ts new file mode 100644 index 0000000000..cdf6aef477 --- /dev/null +++ b/knox-homepage-ui/home/app/util/safehtml.ts @@ -0,0 +1,27 @@ +/* + * 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 {Pipe, PipeTransform} from '@angular/core'; +import {DomSanitizer} from '@angular/platform-browser'; + +@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 431a3889f2..72b9dbb495 100644 --- a/knox-homepage-ui/home/index.html +++ b/knox-homepage-ui/home/index.html @@ -18,43 +18,39 @@ Apache Knox Home - - - + + -