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

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


The following commit(s) were added to refs/heads/main by this push:
     new 8e0fd273 Collect container port information from Docker
8e0fd273 is described below

commit 8e0fd2730ee3c11521cc9196dd48d050f6cb51a6
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Mon Nov 20 15:28:19 2023 -0500

    Collect container port information from Docker
---
 .../camel/karavan/docker/DockerServiceUtils.java   |   6 +-
 .../karavan/infinispan/model/ContainerPort.java    |  62 +++++++++
 .../karavan/infinispan/model/ContainerStatus.java  |  14 +--
 .../karavan/infinispan/model/KaravanSchema.java    |   1 +
 .../src/main/webui/src/api/ProjectModels.ts        |   8 +-
 .../webui/src/containers/ContainerTableRow.tsx     |  30 +++--
 .../knowledgebase/components/ComponentModal.tsx    | 138 +++++++++++++++------
 .../main/webui/src/services/ServicesTableRow.tsx   |  19 ++-
 8 files changed, 220 insertions(+), 58 deletions(-)

diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
index f436b5a8..79cb561d 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerServiceUtils.java
@@ -18,9 +18,11 @@ package org.apache.camel.karavan.docker;
 
 import com.github.dockerjava.api.model.*;
 import io.smallrye.mutiny.tuples.Tuple2;
+import io.vertx.core.json.JsonArray;
 import org.apache.camel.karavan.api.KameletResources;
 import org.apache.camel.karavan.code.model.DockerComposeHealthCheck;
 import org.apache.camel.karavan.infinispan.model.ContainerStatus;
+import org.apache.camel.karavan.infinispan.model.ContainerPort;
 
 import java.io.BufferedReader;
 import java.io.InputStream;
@@ -44,7 +46,9 @@ public class DockerServiceUtils {
 
     protected ContainerStatus getContainerStatus(Container container, String 
environment) {
         String name = container.getNames()[0].replace("/", "");
-        List<Integer> ports = 
Arrays.stream(container.getPorts()).map(ContainerPort::getPrivatePort).filter(Objects::nonNull).collect(Collectors.toList());
+        List<ContainerPort> ports = Arrays.stream(container.getPorts())
+                .map(p -> new ContainerPort(p.getPrivatePort(), 
p.getPublicPort(), p.getType()))
+                .collect(Collectors.toList());
         List<ContainerStatus.Command> commands = 
getContainerCommand(container.getState());
         ContainerStatus.ContainerType type = 
getContainerType(container.getLabels());
         String created = 
Instant.ofEpochSecond(container.getCreated()).toString();
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerPort.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerPort.java
new file mode 100644
index 00000000..af8cebf9
--- /dev/null
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerPort.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package org.apache.camel.karavan.infinispan.model;
+
+import org.infinispan.protostream.annotations.ProtoFactory;
+import org.infinispan.protostream.annotations.ProtoField;
+
+public class ContainerPort {
+
+    @ProtoField(number = 1)
+    Integer privatePort;
+    @ProtoField(number = 2)
+    Integer publicPort;
+    @ProtoField(number = 3)
+    String type;
+
+    @ProtoFactory
+    public ContainerPort(Integer privatePort, Integer publicPort, String type) 
{
+        this.privatePort = privatePort;
+        this.publicPort = publicPort;
+        this.type = type;
+    }
+
+    public Integer getPrivatePort() {
+        return privatePort;
+    }
+
+    public void setPrivatePort(Integer privatePort) {
+        this.privatePort = privatePort;
+    }
+
+    public Integer getPublicPort() {
+        return publicPort;
+    }
+
+    public void setPublicPort(Integer publicPort) {
+        this.publicPort = publicPort;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+}
\ No newline at end of file
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
index b6a4394d..441d78d1 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/ContainerStatus.java
@@ -62,7 +62,7 @@ public class ContainerStatus {
     @ProtoField(number = 4)
     String image;
     @ProtoField(number = 5, collectionImplementation = ArrayList.class)
-    List<Integer> ports;
+    List<ContainerPort> ports;
     @ProtoField(number = 6)
     String env;
     @ProtoField(number = 7)
@@ -93,7 +93,7 @@ public class ContainerStatus {
     String camelRuntime;
 
     @ProtoFactory
-    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<Integer> ports, String env, ContainerType type, 
String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, String phase, Boolean codeLoaded, Boolean 
inTransit, String initDate, String podIP, String camelRuntime) {
+    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<ContainerPort> ports, String env, ContainerType 
type, String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, String phase, Boolean codeLoaded, Boolean 
inTransit, String initDate, String podIP, String camelRuntime) {
         this.projectId = projectId;
         this.containerName = containerName;
         this.containerId = containerId;
@@ -115,7 +115,7 @@ public class ContainerStatus {
         this.camelRuntime = camelRuntime;
     }
 
-    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<Integer> ports, String env, ContainerType type, 
String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, String phase, Boolean codeLoaded, Boolean 
inTransit, String initDate) {
+    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<ContainerPort> ports, String env, ContainerType 
type, String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, String phase, Boolean codeLoaded, Boolean 
inTransit, String initDate) {
         this.projectId = projectId;
         this.containerName = containerName;
         this.containerId = containerId;
@@ -135,7 +135,7 @@ public class ContainerStatus {
         this.initDate = initDate;
     }
 
-    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<Integer> ports, String env, ContainerType type, 
String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, Boolean codeLoaded, Boolean inTransit, 
String camelRuntime) {
+    public ContainerStatus(String projectId, String containerName, String 
containerId, String image, List<ContainerPort> ports, String env, ContainerType 
type, String memoryInfo, String cpuInfo, String created, String finished, 
List<Command> commands, String state, Boolean codeLoaded, Boolean inTransit, 
String camelRuntime) {
         this.projectId = projectId;
         this.containerName = containerName;
         this.containerId = containerId;
@@ -185,7 +185,7 @@ public class ContainerStatus {
         return new ContainerStatus(name, name, null, null, null, env, type, 
null, null, null, null, List.of(Command.run), null, false, false, "");
     }
 
-    public static ContainerStatus createWithId(String projectId, String 
containerName, String env, String containerId, String image, List<Integer> 
ports, ContainerType type, List<Command> commands, String status, String 
created, String camelRuntime) {
+    public static ContainerStatus createWithId(String projectId, String 
containerName, String env, String containerId, String image, 
List<ContainerPort> ports, ContainerType type, List<Command> commands, String 
status, String created, String camelRuntime) {
         return new ContainerStatus(projectId, containerName, containerId, 
image, ports, env, type,
                 null, null, created, null,  commands, status, false, false, 
camelRuntime);
     }
@@ -233,11 +233,11 @@ public class ContainerStatus {
         this.image = image;
     }
 
-    public List<Integer> getPorts() {
+    public List<ContainerPort> getPorts() {
         return ports;
     }
 
-    public void setPorts(List<Integer> ports) {
+    public void setPorts(List<ContainerPort> ports) {
         this.ports = ports;
     }
 
diff --git 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
index f5e2d063..79d5eea3 100644
--- 
a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
+++ 
b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/model/KaravanSchema.java
@@ -31,6 +31,7 @@ import 
org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
                 CamelStatusValue.Name.class,
                 DeploymentStatus.class,
                 ContainerStatus.class,
+                ContainerPort.class,
                 ContainerStatus.ContainerType.class,
                 ContainerStatus.Command.class,
                 ServiceStatus.class
diff --git a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts 
b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
index 70423595..fdafeb9a 100644
--- a/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
+++ b/karavan-web/karavan-app/src/main/webui/src/api/ProjectModels.ts
@@ -79,6 +79,12 @@ export class ServiceStatus {
     type: string = '';
 }
 
+export class ContainerPort {
+    privatePort?: number;
+    publicPort?: number;
+    type: string = '';
+}
+
 export class ContainerStatus {
     containerName: string = '';
     containerId: string = '';
@@ -93,7 +99,7 @@ export class ContainerStatus {
     created: string = '';
     finished: string = '';
     image: string = '';
-    ports: [] = [];
+    ports: ContainerPort [] = [];
     commands: string [] = [];
     inTransit: boolean = false;
     camelRuntime: string = ''
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx 
b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
index d08ee906..5a751f48 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
+++ 
b/karavan-web/karavan-app/src/main/webui/src/containers/ContainerTableRow.tsx
@@ -35,7 +35,7 @@ interface Props {
     container: ContainerStatus
 }
 
-export function ContainerTableRow (props: Props) {
+export function ContainerTableRow(props: Props) {
 
     const [isExpanded, setIsExpanded] = useState<boolean>(false);
     const [showConfirmation, setShowConfirmation] = useState<boolean>(false);
@@ -59,7 +59,8 @@ export function ContainerTableRow (props: Props) {
             actions={[
                 <Button key="confirm" variant="primary" onClick={e => {
                     if (command) {
-                        KaravanApi.manageContainer(container.env, 
container.type, container.containerName, command, res => {});
+                        KaravanApi.manageContainer(container.env, 
container.type, container.containerName, command, res => {
+                        });
                         setCommand(undefined);
                         setShowConfirmation(false);
                     }
@@ -114,7 +115,8 @@ export function ContainerTableRow (props: Props) {
                               spaceItems={{default: 'spaceItemsNone'}}>
                             <FlexItem>
                                 <Tooltip content={"Start container"} 
position={"bottom"}>
-                                    <Button variant={"plain"} 
icon={<PlayIcon/>} isDisabled={!commands.includes('run') || inTransit}
+                                    <Button variant={"plain"} 
icon={<PlayIcon/>}
+                                            
isDisabled={!commands.includes('run') || inTransit}
                                             onClick={e => {
                                                 setCommand('run');
                                                 setShowConfirmation(true);
@@ -123,7 +125,8 @@ export function ContainerTableRow (props: Props) {
                             </FlexItem>
                             <FlexItem>
                                 <Tooltip content={"Pause container"} 
position={"bottom"}>
-                                    <Button variant={"plain"} 
icon={<PauseIcon/>} isDisabled={!commands.includes('pause') || inTransit}
+                                    <Button variant={"plain"} 
icon={<PauseIcon/>}
+                                            
isDisabled={!commands.includes('pause') || inTransit}
                                             onClick={e => {
                                                 setCommand('pause');
                                                 setShowConfirmation(true);
@@ -132,7 +135,8 @@ export function ContainerTableRow (props: Props) {
                             </FlexItem>
                             <FlexItem>
                                 <Tooltip content={"Stop container"} 
position={"bottom"}>
-                                    <Button variant={"plain"} 
icon={<StopIcon/>} isDisabled={!commands.includes('stop') || inTransit}
+                                    <Button variant={"plain"} 
icon={<StopIcon/>}
+                                            
isDisabled={!commands.includes('stop') || inTransit}
                                             onClick={e => {
                                                 setCommand('stop');
                                                 setShowConfirmation(true);
@@ -141,7 +145,8 @@ export function ContainerTableRow (props: Props) {
                             </FlexItem>
                             <FlexItem>
                                 <Tooltip content={"Delete container"} 
position={"bottom"}>
-                                    <Button variant={"plain"} 
icon={<DeleteIcon/>} isDisabled={!commands.includes('delete') || inTransit}
+                                    <Button variant={"plain"} 
icon={<DeleteIcon/>}
+                                            
isDisabled={!commands.includes('delete') || inTransit}
                                             onClick={e => {
                                                 setCommand('delete');
                                                 setShowConfirmation(true);
@@ -157,7 +162,7 @@ export function ContainerTableRow (props: Props) {
                 <Td colSpan={2}>
                     <ExpandableRowContent>
                         <Flex direction={{default: "column"}} 
cellPadding={"0px"}>
-                            {container.containerId?.substring(0, 10)+"..."}
+                            {container.containerId?.substring(0, 10) + "..."}
                         </Flex>
                     </ExpandableRowContent>
                 </Td>
@@ -168,7 +173,16 @@ export function ContainerTableRow (props: Props) {
                 <Td colSpan={2}>
                     <ExpandableRowContent>
                         <Flex direction={{default: "row"}} cellPadding={"0px"}>
-                            {ports.map((port, index) => <FlexItem 
key={index}>{port}</FlexItem>)}
+                            {ports.sort((a, b) => a.privatePort && 
b.privatePort && (a.privatePort > b.privatePort) ? 1 : -1)
+                                .map((port, index) => {
+                                const start = port.publicPort ? 
port.publicPort + "->" : "";
+                                const end = port.privatePort + "/" + port.type;
+                                return (
+                                    <FlexItem key={index}>
+                                        {start + end}
+                                    </FlexItem>
+                                )
+                            })}
                         </Flex>
                     </ExpandableRowContent>
                 </Td>
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentModal.tsx
 
b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentModal.tsx
index da8f1584..acffc979 100644
--- 
a/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentModal.tsx
+++ 
b/karavan-web/karavan-app/src/main/webui/src/knowledgebase/components/ComponentModal.tsx
@@ -14,20 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
+import React, {useState} from 'react';
 import {
     Button,
     Modal,
     ActionGroup,
     Text,
     CardHeader,
-    Badge, Flex, CardTitle,
+    Badge, Flex, CardTitle, Tabs, Tab, TabTitleText,
 } from '@patternfly/react-core';
 import '../../designer/karavan.css';
 import {Table, Tbody, Td, Th, Thead, Tr} from "@patternfly/react-table";
 import {CamelUi} from "../../designer/utils/CamelUi";
 import {ComponentApi} from "karavan-core/lib/api/ComponentApi";
-import {ComponentProperty} from "karavan-core/lib/model/ComponentModels";
+import {ComponentHeader, ComponentProperty} from 
"karavan-core/lib/model/ComponentModels";
 import {useKnowledgebaseStore} from "../KnowledgebaseStore";
 import {shallow} from "zustand/shallow";
 
@@ -36,15 +36,98 @@ export function ComponentModal() {
     const [component, isModalOpen, setModalOpen] = useKnowledgebaseStore((s) =>
         [s.component, s.isModalOpen, s.setModalOpen], shallow)
 
+    const [tab, setTab] = useState<string | number>('properties');
+
     const props = new Map<string, ComponentProperty>();
     if (component) {
         ComponentApi.getComponentProperties(component?.component.name, 
"consumer").forEach(cp => props.set(cp.name, cp));
         ComponentApi.getComponentProperties(component?.component.name, 
"producer").forEach(cp => props.set(cp.name, cp));
     }
+
+    const headers = new Map<string, ComponentHeader>();
+    if (component) {
+        ComponentApi.getComponentHeaders(component?.component.name, 
"consumer").forEach(cp => headers.set(cp.name, cp));
+        ComponentApi.getComponentHeaders(component?.component.name, 
"producer").forEach(cp => headers.set(cp.name, cp));
+    }
+
+
+    function getPropertiesTable() {
+        return (
+            <Table aria-label="Properties table" variant='compact'>
+                <Thead>
+                    <Tr>
+                        <Th key='name'>Display Name / Name</Th>
+                        <Th key='desc'>Description</Th>
+                        <Th key='type'>Type</Th>
+                        <Th key='label'>Label</Th>
+                    </Tr>
+                </Thead>
+                <Tbody>
+                    {Array.from(props.values()).map((p: ComponentProperty, 
idx: number) => (
+                        <Tr key={idx}>
+                            <Td key={`${idx}_name`}>
+                                <div>
+                                    <b>{p.displayName}</b>
+                                    <div>{p.name}</div>
+                                </div>
+                            </Td>
+                            <Td key={`${idx}_desc`}>
+                                <div>
+                                    <div>{p.description}</div>
+                                    {p.defaultValue && 
p.defaultValue.toString().length > 0 &&
+                                        <div>{"Default value: " + 
p.defaultValue}</div>}
+                                </div>
+                            </Td>
+                            <Td key={`${idx}_type`}>{p.type}</Td>
+                            <Td key={`${idx}_label`}>{p.label}</Td>
+                        </Tr>
+                    ))}
+                </Tbody>
+            </Table>
+        )
+    }
+
+    function getHeadersTable() {
+        return (
+            <Table aria-label="Headers table" variant='compact'>
+                <Thead>
+                    <Tr>
+                        <Th key='name'>Name</Th>
+                        <Th key='desc' modifier={"breakWord"}>Description</Th>
+                        <Th key='type'>Group</Th>
+                        <Th key='label'>Java Type</Th>
+                        <Th key='label'>Default Value</Th>
+                        <Th key='label'>Autowired</Th>
+                    </Tr>
+                </Thead>
+                <Tbody>
+                    {Array.from(headers.values()).map((p: ComponentHeader, 
idx: number) => (
+                        <Tr key={idx}>
+                            <Td key={`${idx}_name`}>
+                                <div>
+                                    <b>{p.displayName}</b>
+                                    <div>{p.name}</div>
+                                </div>
+                            </Td>
+                            <Td key={`${idx}_type`}>{p.description}</Td>
+                            <Td key={`${idx}_label`}>{p.group}</Td>
+                            <Td key={`${idx}_label`}>{p.javaType}</Td>
+                            <Td key={`${idx}_label`}>{p.defaultValue}</Td>
+                            <Td key={`${idx}_label`}>{p.autowired}</Td>
+                        </Tr>
+                    ))}
+                </Tbody>
+            </Table>
+        )
+    }
+
+    const showProps = props.size !== 0;
+    const showHeaders = headers.size !== 0;
+
     return (
         <Modal
             aria-label={"Kamelet"}
-            width={'fit-content'}
+            width={'80%'}
             maxLength={200}
             title={component?.component.title}
             isOpen={isModalOpen}
@@ -70,42 +153,17 @@ export function ComponentModal() {
 
                 </CardHeader>
                 <Text 
className="description">{component?.component.description}</Text>
-                {props.size !== 0 &&
-                    <div>
-                        <CardTitle>Properties</CardTitle>
-                        <Table aria-label="Simple table" variant='compact'>
-                            <Thead>
-                                <Tr>
-                                    <Th key='name'>Display Name / Name</Th>
-                                    <Th key='desc'>Description</Th>
-                                    <Th key='type'>Type</Th>
-                                    <Th key='label'>Label</Th>
-                                </Tr>
-                            </Thead>
-                            <Tbody>
-                                {Array.from(props.values()).map((p: 
ComponentProperty, idx: number) => (
-                                    <Tr key={idx}>
-                                        <Td key={`${idx}_name`}>
-                                            <div>
-                                                <b>{p.displayName}</b>
-                                                <div>{p.name}</div>
-                                            </div>
-                                        </Td>
-                                        <Td key={`${idx}_desc`}>
-                                            <div>
-                                                <div>{p.description}</div>
-                                                {p.defaultValue && 
p.defaultValue.toString().length > 0 &&
-                                                    <div>{"Default value: " + 
p.defaultValue}</div>}
-                                            </div>
-                                        </Td>
-                                        <Td key={`${idx}_type`}>{p.type}</Td>
-                                        <Td key={`${idx}_label`}>{p.label}</Td>
-                                    </Tr>
-                                ))}
-                            </Tbody>
-                        </Table>
-                    </div>
-                }
+                <Tabs
+                    activeKey={tab}
+                    onSelect={(event, eventKey) => setTab(eventKey)}
+                    aria-label="Tabs in the default example"
+                    role="region"
+                >
+                    <Tab eventKey={'properties'} 
title={<TabTitleText>Properties</TabTitleText>}/>
+                    <Tab eventKey={'headers'} 
title={<TabTitleText>Headers</TabTitleText>}/>
+                </Tabs>
+                {tab === 'properties' && showProps && getPropertiesTable()}
+                {tab === 'headers' && showHeaders && getHeadersTable()}
             </Flex>
         </Modal>
     )
diff --git 
a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx 
b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
index bfc76b49..cb04b01f 100644
--- a/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
+++ b/karavan-web/karavan-app/src/main/webui/src/services/ServicesTableRow.tsx
@@ -97,6 +97,7 @@ export function ServicesTableRow (props: Props) {
     const env = service.environment;
     const keys = Object.keys(env);
     const container = props.container;
+    const ports = container?.ports || [];
     const isRunning = container?.state === 'running';
     const inTransit = container?.inTransit;
     const color = isRunning ? "green" : "grey";
@@ -144,13 +145,29 @@ export function ServicesTableRow (props: Props) {
             {keys.length > 0 && <Tr isExpanded={isExpanded}>
                 <Td></Td>
                 <Td colSpan={2}>Environment Variables</Td>
-                <Td colSpan={2}>
+                <Td colSpan={1}>
                     <ExpandableRowContent>
                         <Flex direction={{default: "column"}} 
cellPadding={"0px"}>
                         {keys.map(key => <FlexItem key={key}>{key + ": " + 
env[key]}</FlexItem>)}
                         </Flex>
                     </ExpandableRowContent>
                 </Td>
+                <Td colSpan={1}>
+                    <ExpandableRowContent>
+                        <Flex direction={{default: "row"}} cellPadding={"0px"}>
+                            {ports.sort((a, b) => a.privatePort && 
b.privatePort && (a.privatePort > b.privatePort) ? 1 : -1)
+                                .map((port, index) => {
+                                    const start = port.publicPort ? 
port.publicPort + "->" : "";
+                                    const end = port.privatePort + "/" + 
port.type;
+                                    return (
+                                        <FlexItem key={index}>
+                                            {start + end}
+                                        </FlexItem>
+                                    )
+                                })}
+                        </Flex>
+                    </ExpandableRowContent>
+                </Td>
             </Tr>}
             {healthcheck && <Tr isExpanded={isExpanded}>
                 <Td></Td>

Reply via email to