From 7b13ec19e27f7f870b7e24342bfea33323111f59 Mon Sep 17 00:00:00 2001 From: benniekiss <63211101+benniekiss@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:28:30 -0400 Subject: [PATCH] Render ports as ranges when applicable When a user specifies multiple overlapping ports with the same host, host ip and protocol collapse them into a range. --- src/ContainerIntegration.jsx | 53 ++++++++++++++++++++++++++++++++---- test/check-application | 16 +++++++++++ test/reference | 2 +- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/ContainerIntegration.jsx b/src/ContainerIntegration.jsx index f2ed525f2..7f1b405ba 100644 --- a/src/ContainerIntegration.jsx +++ b/src/ContainerIntegration.jsx @@ -15,17 +15,58 @@ export const renderContainerPublishedPorts = ports => { if (!ports) return null; + const ranges = []; const items = []; + + // create port ranges Object.entries(ports).forEach(([containerPort, hostBindings]) => { - (hostBindings ?? []).forEach(binding => { // not-covered: null was observed in the wild, but unknown how to reproduce - items.push( - - { binding.HostIp || "0.0.0.0" }:{ binding.HostPort } → { containerPort } - - ); + const [port, proto] = containerPort.split('/'); + const portNumber = Number(port); + // not-covered: null was observed in the wild, but unknown how to reproduce + (hostBindings ?? []).forEach(binding => { + const lastRange = ranges[ranges.length - 1]; + const hostIP = binding.HostIp || "0.0.0.0"; + const hostPort = Number(binding.HostPort); + + let isPortConsecutive = false; + let isHostPortConsecutive = false; + let isSameHostIP = false; + let isSameProtocol = false; + + if (lastRange) { + isPortConsecutive = portNumber === lastRange.endPort + 1; + isHostPortConsecutive = hostPort === lastRange.hostEndPort + 1; + isSameHostIP = hostIP === lastRange.hostIp; + isSameProtocol = proto === lastRange.protocol; + } + + if (isPortConsecutive && isHostPortConsecutive && isSameHostIP && isSameProtocol) { + // ports are consecutive, so extend the range + lastRange.endPort = portNumber; + lastRange.hostEndPort = hostPort; + } else { + // ports are not consecutive, so start a new range + ranges.push({ + startPort: portNumber, + endPort: portNumber, + protocol: proto, + hostIp: hostIP, + hostStartPort: hostPort, + hostEndPort: hostPort + }); + } }); }); + // create list items based on the ranges + ranges.forEach(({ startPort, endPort, protocol, hostIp, hostStartPort, hostEndPort }) => { + items.push( + + {hostIp}:{hostStartPort}{hostStartPort !== hostEndPort ? `-${hostEndPort}` : ''} → {startPort}{startPort !== endPort ? `-${endPort}` : ''}/{protocol} + + ); + }); + return {items}; }; diff --git a/test/check-application b/test/check-application index 1c4f012b4..70d81263b 100755 --- a/test/check-application +++ b/test/check-application @@ -1915,6 +1915,19 @@ class TestApplication(testlib.MachineCase): b.click('.publish-port-form .btn-add') b.set_input_text('#run-image-dialog-publish-4-ip-address', '127.0.0.2') b.set_input_text('#run-image-dialog-publish-4-container-port', '9001') + # publish range of ports + b.click('.publish-port-form .btn-add') + b.set_input_text('#run-image-dialog-publish-5-container-port', '4000') + b.set_input_text('#run-image-dialog-publish-5-host-port', '4000') + b.click('.publish-port-form .btn-add') + b.set_input_text('#run-image-dialog-publish-6-container-port', '4001') + b.set_input_text('#run-image-dialog-publish-6-host-port', '4001') + b.click('.publish-port-form .btn-add') + b.set_input_text('#run-image-dialog-publish-7-container-port', '4002') + b.set_input_text('#run-image-dialog-publish-7-host-port', '4002') + b.click('.publish-port-form .btn-add') + b.set_input_text('#run-image-dialog-publish-8-container-port', '4003') + b.set_input_text('#run-image-dialog-publish-8-host-port', '4003') # Configure env b.click('.env-form .btn-add') @@ -2033,11 +2046,14 @@ class TestApplication(testlib.MachineCase): '127.0.0.2:') b.wait_in_text(f'#containers-containers tr:contains("{IMG_BUSYBOX}") dt:contains("Ports") + dd', ' \u2192 8001/tcp') + b.wait_in_text(f'#containers-containers tr:contains("{IMG_BUSYBOX}") dt:contains("Ports") + dd', + '0.0.0.0:4000-4003 \u2192 4000-4003/tcp') b.wait_not_in_text(f'#containers-containers tr:contains("{IMG_BUSYBOX}") dt:contains("Ports") + dd', '7001/tcp') ports = self.execute(auth, "podman inspect --format '{{.NetworkSettings.Ports}}' busybox-with-tty") self.assertRegex(ports, r'5000/tcp:\[{(0.0.0.0)? 6000}\]') + self.assertRegex(ports, r'400[0-3]?/tcp:\[{(0.0.0.0)? 400[0-3]?}\]') self.assertIn('5001/udp:[{127.0.0.1 6001}]', ports) self.assertIn('8001/tcp:[{', ports) self.assertIn('9001/tcp:[{127.0.0.2 ', ports) diff --git a/test/reference b/test/reference index 618b490bd..cf4bc98ca 160000 --- a/test/reference +++ b/test/reference @@ -1 +1 @@ -Subproject commit 618b490bd3db1fc4a106652d5fb708a8e4309207 +Subproject commit cf4bc98caa368b3907caa697d6ac0861ebc8a25a