From c3419524e5cc210e9709ca97ab8e77b2480dd0e0 Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Sat, 29 Aug 2026 21:25:58 -0400 Subject: [PATCH] Fixed the duplicate portmappings caused by Docker's /containers/json API returning two Ports entries for every bound port --- DEVLOG.md | 11 +++++++++++ README.md | 2 +- main.cpp | 2 +- src/container_parser.cpp | 7 ++++++- tests/test_container_parser.cpp | 7 +++++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 87f9604..58906d5 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,16 @@ # Tux-Dock Development Log +## 0.4.1-beta + +### TUI stability + +- Fixed duplicate port mappings shown in the container list TUI. Docker's `/containers/json` API returns separate `Ports` entries for IPv4 (`0.0.0.0`) and IPv6 (`::`) bindings of the same host port; the container parser now deduplicates these into a single `public:private` display string. +- Added a container parser regression test covering IPv4/IPv6 duplicate port bindings. + +### Version + +- Bumped version to `0.4.1-beta`. + ## 0.4-beta ### ARM64 / tuxreaperd fixes diff --git a/README.md b/README.md index fa3be1b..49b27f2 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ ctest --test-dir build --output-on-failure ## About / Version -- Version: `0.4-beta` +- Version: `0.4.1-beta` - Created by: `markmental` - GitHub: https://github.com/MARKMENTAL/tuxdock - Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock diff --git a/main.cpp b/main.cpp index 18069e4..257b4aa 100644 --- a/main.cpp +++ b/main.cpp @@ -717,7 +717,7 @@ void TuxDockApp::ActionExecDetachedCommand() { } void TuxDockApp::ActionAbout() { - OpenMessage("About Tux-Dock", "Tux-Dock 0.4-beta | Created by markmental"); + OpenMessage("About Tux-Dock", "Tux-Dock 0.4.1-beta | Created by markmental"); } void TuxDockApp::ExecuteSelectedAction() { diff --git a/src/container_parser.cpp b/src/container_parser.cpp index 8fc5b04..69f6f80 100644 --- a/src/container_parser.cpp +++ b/src/container_parser.cpp @@ -3,6 +3,7 @@ #include #include +#include using json = nlohmann::json; @@ -17,6 +18,7 @@ std::string portsFor(const json& item) { const auto it = item.find("Ports"); if (it == item.end() || !it->is_array()) return {}; std::ostringstream result; + std::unordered_set seen; bool first = true; for (const auto& port : *it) { if (!port.is_object()) continue; @@ -24,8 +26,11 @@ std::string portsFor(const json& item) { const auto private_it = port.find("PrivatePort"); if (public_it == port.end() || private_it == port.end() || !public_it->is_number() || !private_it->is_number()) continue; + const std::string mapping = std::to_string(public_it->get()) + ":" + + std::to_string(private_it->get()); + if (!seen.insert(mapping).second) continue; if (!first) result << ", "; - result << *public_it << ":" << *private_it; + result << mapping; first = false; } return result.str(); diff --git a/tests/test_container_parser.cpp b/tests/test_container_parser.cpp index 79d0ff9..69bb880 100644 --- a/tests/test_container_parser.cpp +++ b/tests/test_container_parser.cpp @@ -7,15 +7,18 @@ int main() { const auto result = parseContainerList(R"([ {"Id":"running-id","Names":["/web"],"State":"running","Status":"Up 2 minutes","Ports":[]}, {"Id":"exited-id","Names":["/old"],"State":"exited","Status":"Exited (0) 1 minute ago","Ports":[{"PrivatePort":80,"Type":"tcp"}]}, - {"Id":"odd-id","Names":["/odd"],"State":"exited","Ports":"unexpected"} + {"Id":"odd-id","Names":["/odd"],"State":"exited","Ports":"unexpected"}, + {"Id":"mapped-id","Names":["/mapped"],"State":"running","Status":"Up 1 minute","Ports":[{"IP":"0.0.0.0","PrivatePort":80,"PublicPort":8080,"Type":"tcp"},{"IP":"::","PrivatePort":80,"PublicPort":8080,"Type":"tcp"}]} ])"); assert(result.ok()); - assert(result.items.size() == 3); + assert(result.items.size() == 4); assert(result.items[0].running); assert(!result.items[1].running); assert(result.items[1].name == "old"); assert(result.items[1].ports.empty()); assert(result.items[2].name == "odd"); + assert(result.items[3].name == "mapped"); + assert(result.items[3].ports == "8080:80"); const auto empty = parseContainerList("[]"); assert(empty.ok());