Skip to content

Commit

Permalink
Render ports as ranges when applicable
Browse files Browse the repository at this point in the history
When a user specifies multiple overlapping ports with the same host,
host ip and protocol collapse them into a range.
  • Loading branch information
benniekiss authored and jelly committed Sep 4, 2024
1 parent 2c31f65 commit f020900
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 7 deletions.
53 changes: 47 additions & 6 deletions src/ContainerIntegration.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<ListItem key={ containerPort + binding.HostIp + binding.HostPort }>
{ binding.HostIp || "0.0.0.0" }:{ binding.HostPort } &rarr; { containerPort }
</ListItem>
);
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(
<ListItem key={ startPort + hostIp + hostStartPort }>
{hostIp}:{hostStartPort}{hostStartPort !== hostEndPort ? `-${hostEndPort}` : ''} &rarr; {startPort}{startPort !== endPort ? `-${endPort}` : ''}/{protocol}
</ListItem>
);
});

return <List isPlain>{items}</List>;
};

Expand Down
16 changes: 16 additions & 0 deletions test/check-application
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f020900

Please sign in to comment.