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

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


The following commit(s) were added to refs/heads/master by this push:
     new 20fa65948 KNOX-3003 - Services with more than one serviceUrl metadata 
are grouped on the Knox Home page (#838)
20fa65948 is described below

commit 20fa65948804a4ddedd246c61d896005c47b0104
Author: Sandor Molnar <[email protected]>
AuthorDate: Fri Feb 2 12:24:26 2024 +0100

    KNOX-3003 - Services with more than one serviceUrl metadata are grouped on 
the Knox Home page (#838)
---
 .../service/metadata/KnoxMetadataResource.java     | 10 ++-
 .../gateway/service/metadata/ServiceModel.java     | 79 +++++++++++++---------
 .../gateway/service/metadata/ServiceModelTest.java | 26 ++++---
 knox-homepage-ui/home/app/topologies/service.ts    |  2 +-
 .../topologies/topology.information.component.css  |  4 ++
 .../topologies/topology.information.component.html | 76 +++++++++++++++++----
 .../topologies/topology.information.component.ts   | 27 ++++++++
 7 files changed, 163 insertions(+), 61 deletions(-)

diff --git 
a/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
 
b/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
index ec826c62b..d50283c39 100644
--- 
a/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
+++ 
b/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/KnoxMetadataResource.java
@@ -229,9 +229,8 @@ public class KnoxMetadataResource {
           Set<ServiceModel> apiServices = new HashSet<>();
           Set<ServiceModel> uiServices = new HashSet<>();
           topology.getServices().stream().filter(service -> 
!UNREAL_SERVICES.contains(service.getRole())).forEach(service -> {
-            service.getUrls().forEach(serviceUrl -> {
-              ServiceModel serviceModel = getServiceModel(request, 
config.getGatewayPath(), topology.getName(), service, 
getServiceMetadata(serviceDefinitionRegistry, service),
-                  serviceUrl);
+            if (!service.getUrls().isEmpty()) {
+              final ServiceModel serviceModel = getServiceModel(request, 
config.getGatewayPath(), topology.getName(), service, 
getServiceMetadata(serviceDefinitionRegistry, service));
               if (ServiceModel.Type.UI == serviceModel.getType()) {
                 uiServices.add(serviceModel);
               } else if (ServiceModel.Type.API_AND_UI == 
serviceModel.getType()) {
@@ -240,7 +239,7 @@ public class KnoxMetadataResource {
               } else {
                 apiServices.add(serviceModel);
               }
-            });
+            }
           });
           topologies.addTopology(topology.getName(), 
isPinnedTopology(topology.getName(), config), 
config.getApiServicesViewVersionOnHomepage(), new TreeSet<>(apiServices), new 
TreeSet<>(uiServices));
         }
@@ -264,14 +263,13 @@ public class KnoxMetadataResource {
     return serviceDefinition.isPresent() ? 
serviceDefinition.get().getService().getMetadata() : null;
   }
 
-  private ServiceModel getServiceModel(HttpServletRequest request, String 
gatewayPath, String topologyName, Service service, Metadata serviceMetadata, 
String serviceUrl) {
+  private ServiceModel getServiceModel(HttpServletRequest request, String 
gatewayPath, String topologyName, Service service, Metadata serviceMetadata) {
     final ServiceModel serviceModel = new ServiceModel();
     serviceModel.setRequest(request);
     serviceModel.setGatewayPath(gatewayPath);
     serviceModel.setTopologyName(topologyName);
     serviceModel.setService(service);
     serviceModel.setServiceMetadata(serviceMetadata);
-    serviceModel.setServiceUrl(serviceUrl);
     return serviceModel;
   }
 
diff --git 
a/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/ServiceModel.java
 
b/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/ServiceModel.java
index 5e35d27ff..600347531 100644
--- 
a/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/ServiceModel.java
+++ 
b/gateway-service-metadata/src/main/java/org/apache/knox/gateway/service/metadata/ServiceModel.java
@@ -17,12 +17,18 @@
  */
 package org.apache.knox.gateway.service.metadata;
 
+import static java.lang.String.format;
+import static java.util.Locale.ROOT;
+
 import java.io.UncheckedIOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.xml.bind.annotation.XmlAccessType;
@@ -60,7 +66,6 @@ public class ServiceModel implements Comparable<ServiceModel> 
{
   private String gatewayPath;
   private Service service;
   private Metadata serviceMetadata;
-  private String serviceUrl;
 
   public void setRequest(HttpServletRequest request) {
     this.request = request;
@@ -82,10 +87,6 @@ public class ServiceModel implements 
Comparable<ServiceModel> {
     this.serviceMetadata = serviceMetadata;
   }
 
-  public void setServiceUrl(String serviceUrl) {
-    this.serviceUrl = serviceUrl;
-  }
-
   @XmlElement
   public String getServiceName() {
     return this.service == null ? "" : service.getRole();
@@ -124,40 +125,52 @@ public class ServiceModel implements 
Comparable<ServiceModel> {
     return serviceMetadata == null ? ("/" + 
getServiceName().toLowerCase(Locale.ROOT)) : serviceMetadata.getContext();
   }
 
-  @XmlElement
-  public String getServiceUrl() {
-    String context = getContext();
+  @XmlElement(name = "serviceUrls")
+  public List<String> getServiceUrls() {
+    final Set<String> resolvedServiceUrls = new TreeSet<>();
+    final String context = getContext();
+
     if (HIVE_SERVICE_NAME.equals(getServiceName())) {
-      return String.format(Locale.ROOT, HIVE_SERVICE_URL_TEMPLATE, 
request.getServerName(), request.getServerPort(), gatewayPath, topologyName);
+      resolvedServiceUrls.add(format(ROOT, HIVE_SERVICE_URL_TEMPLATE, 
request.getServerName(), request.getServerPort(), gatewayPath, topologyName));
     } else if (IMPALA_SERVICE_NAME.equals(getServiceName())) {
-      return String.format(Locale.ROOT, IMPALA_SERVICE_URL_TEMPLATE, 
request.getServerName(), request.getServerPort(), gatewayPath, topologyName);
+      resolvedServiceUrls.add(format(ROOT, IMPALA_SERVICE_URL_TEMPLATE, 
request.getServerName(), request.getServerPort(), gatewayPath, topologyName));
     } else {
-      return getServiceUrl(context);
+      if (service != null && service.getUrls() != null && 
!service.getUrls().isEmpty()) {
+        this.service.getUrls().forEach(serviceUrl -> {
+          resolvedServiceUrls.add(getServiceUrl(context, serviceUrl));
+        });
+      } else {
+        // fall back to the service URL fetched from the 'service' instance, 
if any
+        resolvedServiceUrls.add(getServiceUrl(context, null));
+      }
     }
+    return Arrays.asList(resolvedServiceUrls.toArray(new String[0]));
   }
 
-  private String getServiceUrl(String context) {
-    final String resolvedContext = resolvePlaceholdersFromBackendUrl(context);
+  private String getServiceUrl(String context, String serviceUrl) {
+    final String resolvedContext = resolvePlaceholdersFromBackendUrl(context, 
serviceUrl);
     return String.format(Locale.ROOT, SERVICE_URL_TEMPLATE, 
request.getScheme(), request.getServerName(), request.getServerPort(), 
gatewayPath, topologyName, resolvedContext);
   }
 
-  private String resolvePlaceholdersFromBackendUrl(String resolveable) {
+  private String resolvePlaceholdersFromBackendUrl(String resolveable, String 
serviceUrl) {
     String toBeResolved = resolveable;
     if (toBeResolved != null) {
-      final String backendUrlString = getBackendServiceUrl();
+      final String backendUrlString = getBackendServiceUrl(serviceUrl);
 
-      if (toBeResolved.indexOf("{{BACKEND_HOST}}") > -1) {
-        toBeResolved = toBeResolved.replace("{{BACKEND_HOST}}", 
backendUrlString);
-      }
+      if (StringUtils.isNotBlank(backendUrlString)) {
+        if (toBeResolved.indexOf("{{BACKEND_HOST}}") > -1) {
+          toBeResolved = toBeResolved.replace("{{BACKEND_HOST}}", 
backendUrlString);
+        }
 
-      if (toBeResolved.indexOf("{{SCHEME}}") > -1 || 
toBeResolved.indexOf("{{HOST}}") > -1 || toBeResolved.indexOf("{{PORT}}") > -1) 
{
-        try {
-          final URL backendUrl = new URL(backendUrlString);
-          toBeResolved = toBeResolved.replace("{{SCHEME}}", 
backendUrl.getProtocol());
-          toBeResolved = toBeResolved.replace("{{HOST}}", 
backendUrl.getHost());
-          toBeResolved = toBeResolved.replace("{{PORT}}", 
String.valueOf(backendUrl.getPort()));
-        } catch (MalformedURLException e) {
-          throw new UncheckedIOException("Error while converting " + 
backendUrlString + " to a URL", e);
+        if (toBeResolved.indexOf("{{SCHEME}}") > -1 || 
toBeResolved.indexOf("{{HOST}}") > -1 || toBeResolved.indexOf("{{PORT}}") > -1) 
{
+          try {
+            final URL backendUrl = new URL(backendUrlString);
+            toBeResolved = toBeResolved.replace("{{SCHEME}}", 
backendUrl.getProtocol());
+            toBeResolved = toBeResolved.replace("{{HOST}}", 
backendUrl.getHost());
+            toBeResolved = toBeResolved.replace("{{PORT}}", 
String.valueOf(backendUrl.getPort()));
+          } catch (MalformedURLException e) {
+            throw new UncheckedIOException("Error while converting '" + 
backendUrlString + "' to a URL", e);
+          }
         }
       }
     }
@@ -165,7 +178,7 @@ public class ServiceModel implements 
Comparable<ServiceModel> {
     return toBeResolved;
   }
 
-  String getBackendServiceUrl() {
+  String getBackendServiceUrl(String serviceUrl) {
     final String backendServiceUrl = serviceUrl == null ? (service == null ? 
"" : service.getUrl()) : serviceUrl;
     return backendServiceUrl == null ? "" : backendServiceUrl;
   }
@@ -183,7 +196,9 @@ public class ServiceModel implements 
Comparable<ServiceModel> {
         } else {
           final String method = StringUtils.isBlank(sample.getMethod()) ? 
"GET" : sample.getMethod();
           final String path = sample.getPath().startsWith("/") ? 
sample.getPath() : ("/" + sample.getPath());
-          resolvedSample.setValue(String.format(Locale.ROOT, 
CURL_SAMPLE_TEMPLATE, method, getServiceUrl(), path));
+          final String serviceUrl = getServiceUrls().isEmpty() ? (service == 
null ? "$SERVICE_URL" : service.getUrl())
+              : getServiceUrls().stream().findFirst().get();
+          resolvedSample.setValue(String.format(Locale.ROOT, 
CURL_SAMPLE_TEMPLATE, method, serviceUrl, path));
         }
         samples.add(resolvedSample);
       });
@@ -201,19 +216,19 @@ public class ServiceModel implements 
Comparable<ServiceModel> {
     }
     final ServiceModel serviceModel = (ServiceModel) obj;
     return new EqualsBuilder().append(topologyName, 
serviceModel.topologyName).append(gatewayPath, 
serviceModel.gatewayPath).append(getServiceName(), 
serviceModel.getServiceName())
-        .append(getVersion(), 
serviceModel.getVersion()).append(serviceMetadata, 
serviceModel.serviceMetadata).append(getServiceUrl(), 
serviceModel.getServiceUrl()).isEquals();
+        .append(getVersion(), 
serviceModel.getVersion()).append(serviceMetadata, 
serviceModel.serviceMetadata).append(getServiceUrls(), 
serviceModel.getServiceUrls()).isEquals();
   }
 
   @Override
   public int hashCode() {
-    return new HashCodeBuilder(17, 
37).append(topologyName).append(gatewayPath).append(getServiceName()).append(getVersion()).append(serviceMetadata).append(getServiceUrl())
+    return new HashCodeBuilder(17, 
37).append(topologyName).append(gatewayPath).append(getServiceName()).append(getVersion()).append(serviceMetadata).append(getServiceUrls())
         .toHashCode();
   }
 
   @Override
   public String toString() {
     return new ToStringBuilder(this, 
ToStringStyle.SHORT_PREFIX_STYLE).append(topologyName).append(gatewayPath).append(getServiceName()).append(getVersion())
-        .append(serviceMetadata).append(getServiceUrl()).toString();
+        .append(serviceMetadata).append(getServiceUrls()).toString();
   }
 
   @Override
@@ -221,7 +236,7 @@ public class ServiceModel implements 
Comparable<ServiceModel> {
     final int byServiceName = 
getServiceName().compareTo(other.getServiceName());
     if (byServiceName == 0) {
       final int byVersion = getVersion().compareTo(getVersion());
-      return byVersion == 0 ? 
getBackendServiceUrl().compareTo(other.getBackendServiceUrl()) : byVersion;
+      return byVersion == 0 ? Integer.compare(getServiceUrls().size(), 
other.getServiceUrls().size()) : byVersion;
     }
     return byServiceName;
   }
diff --git 
a/gateway-service-metadata/src/test/java/org/apache/knox/gateway/service/metadata/ServiceModelTest.java
 
b/gateway-service-metadata/src/test/java/org/apache/knox/gateway/service/metadata/ServiceModelTest.java
index 77ff7c3e2..9db82fc31 100644
--- 
a/gateway-service-metadata/src/test/java/org/apache/knox/gateway/service/metadata/ServiceModelTest.java
+++ 
b/gateway-service-metadata/src/test/java/org/apache/knox/gateway/service/metadata/ServiceModelTest.java
@@ -158,7 +158,7 @@ public class ServiceModelTest {
     serviceModel.setService(service);
     EasyMock.expect(service.getRole()).andReturn(HIVE_SERVICE_NAME).anyTimes();
     EasyMock.replay(service);
-    assertEquals(String.format(Locale.ROOT, HIVE_SERVICE_URL_TEMPLATE, 
SERVER_NAME, SERVER_PORT, gatewayPath, topologyName), 
serviceModel.getServiceUrl());
+    assertEquals(String.format(Locale.ROOT, HIVE_SERVICE_URL_TEMPLATE, 
SERVER_NAME, SERVER_PORT, gatewayPath, topologyName), 
getFirstServiceUrl(serviceModel));
   }
 
   @Test
@@ -173,7 +173,7 @@ public class ServiceModelTest {
     serviceModel.setService(service);
     
EasyMock.expect(service.getRole()).andReturn(IMPALA_SERVICE_NAME).anyTimes();
     EasyMock.replay(service);
-    assertEquals(String.format(Locale.ROOT, IMPALA_SERVICE_URL_TEMPLATE, 
SERVER_NAME, SERVER_PORT, gatewayPath, topologyName), 
serviceModel.getServiceUrl());
+    assertEquals(String.format(Locale.ROOT, IMPALA_SERVICE_URL_TEMPLATE, 
SERVER_NAME, SERVER_PORT, gatewayPath, topologyName), 
getFirstServiceUrl(serviceModel));
   }
 
   public HttpServletRequest setUpHttpRequestMock() {
@@ -198,7 +198,7 @@ public class ServiceModelTest {
     final String context = "/testContext";
     EasyMock.expect(metadata.getContext()).andReturn(context).anyTimes();
     EasyMock.replay(metadata);
-    assertEquals(String.format(Locale.ROOT, SERVICE_URL_TEMPLATE, 
SERVER_SCHEME, SERVER_NAME, SERVER_PORT, gatewayPath, topologyName, context), 
serviceModel.getServiceUrl());
+    assertEquals(String.format(Locale.ROOT, SERVICE_URL_TEMPLATE, 
SERVER_SCHEME, SERVER_NAME, SERVER_PORT, gatewayPath, topologyName, context), 
getFirstServiceUrl(serviceModel));
   }
 
   @Test
@@ -214,20 +214,24 @@ public class ServiceModelTest {
     serviceModel.setService(service);
     EasyMock.expect(service.getRole()).andReturn("service").anyTimes();
     final String backendHost = "https://localhost:5555";;
-    EasyMock.expect(service.getUrl()).andReturn(backendHost); // backend host 
comes from the service object
+    EasyMock.expect(service.getUrl()).andReturn(backendHost).anyTimes(); // 
backend host comes from the service object
     final Metadata metadata = EasyMock.createNiceMock(Metadata.class);
     serviceModel.setServiceMetadata(metadata);
     final String context = "/testContext?backendHost={{BACKEND_HOST}}";
     EasyMock.expect(metadata.getContext()).andReturn(context).anyTimes();
     EasyMock.replay(service, metadata);
     assertEquals(String.format(Locale.ROOT, SERVICE_URL_TEMPLATE, 
SERVER_SCHEME, SERVER_NAME, SERVER_PORT, gatewayPath, topologyName,
-        context.replace("{{BACKEND_HOST}}", backendHost)), 
serviceModel.getServiceUrl());
+        context.replace("{{BACKEND_HOST}}", backendHost)), 
getFirstServiceUrl(serviceModel));
 
     final String serviceUrl = "https://serviceHost:8888";;
-    serviceModel.setServiceUrl(serviceUrl); // backend host comes from the 
given service URL
+    final Service serviceWithServiceUrls = 
EasyMock.createNiceMock(Service.class);
+    // backend host comes from the given service URLs
+    
EasyMock.expect(serviceWithServiceUrls.getUrls()).andReturn(Arrays.asList(serviceUrl)).anyTimes();
+    EasyMock.replay(serviceWithServiceUrls);
+    serviceModel.setService(serviceWithServiceUrls);
     assertEquals(
         String.format(Locale.ROOT, SERVICE_URL_TEMPLATE, SERVER_SCHEME, 
SERVER_NAME, SERVER_PORT, gatewayPath, topologyName, 
context.replace("{{BACKEND_HOST}}", serviceUrl)),
-        serviceModel.getServiceUrl());
+        getFirstServiceUrl(serviceModel));
   }
 
   @Test
@@ -242,7 +246,7 @@ public class ServiceModelTest {
     final Service service = EasyMock.createNiceMock(Service.class);
     serviceModel.setService(service);
     EasyMock.expect(service.getRole()).andReturn("service").anyTimes();
-    EasyMock.expect(service.getUrl()).andReturn("https://localhost:5555";);
+    
EasyMock.expect(service.getUrl()).andReturn("https://localhost:5555";).anyTimes();
 
     final Metadata metadata = EasyMock.createNiceMock(Metadata.class);
     serviceModel.setServiceMetadata(metadata);
@@ -251,7 +255,7 @@ public class ServiceModelTest {
 
     EasyMock.replay(service, metadata);
     assertEquals(String.format(Locale.ROOT, SERVICE_URL_TEMPLATE, 
SERVER_SCHEME, SERVER_NAME, SERVER_PORT, gatewayPath, topologyName,
-        context.replace("{{SCHEME}}", "https").replace("{{HOST}}", 
"localhost").replace("{{PORT}}", "5555")), serviceModel.getServiceUrl());
+        context.replace("{{SCHEME}}", "https").replace("{{HOST}}", 
"localhost").replace("{{PORT}}", "5555")), getFirstServiceUrl(serviceModel));
   }
 
   @Test
@@ -285,4 +289,8 @@ public class ServiceModelTest {
     assertEquals(samples.get(0).getValue(), "curl -iv -X PUT 
\"https://localhost:8443/gateway/sandbox/testContext/sampleStartsWithSlashPath/operation\"";);
     assertEquals(samples.get(1).getValue(), "curl -iv -X GET 
\"https://localhost:8443/gateway/sandbox/testContext/sampleStartsWithoutSlashPath/operation\"";);
   }
+
+  private String getFirstServiceUrl(ServiceModel serviceModel) {
+    return serviceModel.getServiceUrls().isEmpty() ? "" : 
serviceModel.getServiceUrls().get(0);
+  }
 }
diff --git a/knox-homepage-ui/home/app/topologies/service.ts 
b/knox-homepage-ui/home/app/topologies/service.ts
index dca781aef..d38b17f13 100644
--- a/knox-homepage-ui/home/app/topologies/service.ts
+++ b/knox-homepage-ui/home/app/topologies/service.ts
@@ -20,7 +20,7 @@ export class Service {
     description: string;
     serviceName: string;
     version: string;
-    serviceUrl: string;
+    serviceUrls: string[];
     shortDesc: string;
     type: string;
     context: string;
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 eb2de0363..5d2f9cd2e 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.component.css
+++ b/knox-homepage-ui/home/app/topologies/topology.information.component.css
@@ -34,3 +34,7 @@ mat-grid-tile-footer figcaption h3 {
   transition: all 0.3s linear;
   visibility: visible;
 }
+
+/deep/ .groupServiceInformationModal {
+    width: 80%;
+}
\ 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 9086f087b..510945441 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.component.html
+++ b/knox-homepage-ui/home/app/topologies/topology.information.component.html
@@ -38,18 +38,31 @@
         <!-- UI services -->
         <mat-grid-list cols="4" rowHeight="130px">
             <mat-grid-tile *ngFor="let service of topology.uiServices.service" 
[colspan]="1" [rowspan]="1">
-                 <a href="{{service.serviceUrl}}" target="_blank" 
*ngIf="!this['enableServiceText_' + service.serviceName.toLowerCase()]">
+
+                <!-- 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()]">
+                        {{service.shortDesc}}
+                    </a>
+                    <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)="openGroupServiceInformationModal(service)">
                     <img 
src="assets/service-logos/{{service.serviceName.toLowerCase()}}.png" 
height="50px" (error)="enableServiceText('enableServiceText_' + 
service.serviceName.toLowerCase())"/>
-                </a>
-                 <a href="{{service.serviceUrl}}" target="_blank" 
*ngIf="this['enableServiceText_' + service.serviceName.toLowerCase()]">
-                    {{service.shortDesc}}
-                </a>
-                <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-footer class="tile-shortDesc">
+                        <h3>{{service.shortDesc}} 
({{service.serviceUrls.length}})</h3>
+                    </mat-grid-tile-footer>
+                </div>
+
             </mat-grid-tile>
         </mat-grid-list>
 
@@ -75,7 +88,10 @@
                         {{service.shortDesc}} <span class="small" 
*ngIf="service.version">(v{{service.version}})</span>
                     </td>
                     <td>
-                        <a 
href="{{service.serviceUrl}}">{{service.serviceUrl}}</a>
+                        <ng-container *ngFor="let serviceUrl of 
service.serviceUrls">
+                            <a href="{{serviceUrl}}">{{serviceUrl}}</a>
+                            <br *ngIf="service.serviceUrls.length > 1" />
+                        </ng-container>
                     </td>
                 </tr>
             </tbody>
@@ -141,7 +157,7 @@
                        <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>&nbsp;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.serviceUrl}}" 
target="_blank">{{selectedApiService.serviceUrl}}</a></td>
+                           <td>&nbsp;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>
@@ -149,8 +165,42 @@
                </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>
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 d267f7566..bbb18763b 100644
--- a/knox-homepage-ui/home/app/topologies/topology.information.component.ts
+++ b/knox-homepage-ui/home/app/topologies/topology.information.component.ts
@@ -34,9 +34,14 @@ export class TopologyInformationsComponent implements OnInit 
{
     @ViewChild('apiServiceInformationModal')
     apiServiceInformationModal: BsModalComponent;
 
+    @ViewChild('groupServiceInformationModal')
+    groupServiceInformationModal: BsModalComponent;
+
     topologies: TopologyInformation[];
     desiredTopologies: string[];
     selectedApiService: Service;
+    selectedGroupService: Service;
+    filteredServiceUrls: string[];
 
     setTopologies(topologies: TopologyInformation[]) {
         this.topologies = topologies;
@@ -106,4 +111,26 @@ export class TopologyInformationsComponent implements 
OnInit {
         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 ) {
+            return ' - ' + serviceUrl.slice(hostStart).replace('host=', 
'').replace('&port=', ':')
+                   .replace('https://', '').replace('http://', '');
+        }
+           return '';
+    }
+
+    filterServiceUrls(filterText: string): void {
+           if (filterText === '') {
+                   this.filteredServiceUrls = 
this.selectedGroupService.serviceUrls;
+           } else {
+                   this.filteredServiceUrls = 
this.selectedGroupService.serviceUrls.filter(serviceUrl => 
serviceUrl.includes(filterText));
+        }
+    }
 }

Reply via email to