From 88c03d143155f2d6ff560cf2283261ab69b4b236 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 12 Jul 2026 21:57:47 +0800 Subject: [PATCH] fix(marker): restore markers from topology on project load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _create_link_from_topology_data now restores traffic-insight markers (mirroring how filters are restored via update_filters), so markers — including their color — survive project close/reopen and server restart. Guard start_marker's uBridge POST with 'if self._created' (exactly as update_filters guards its update() call): during project load the link is not yet created, so only _markers state is recorded and the marker is applied once via the NIO flow in create()/_ubridge_apply_markers — no double application. --- gns3server/controller/project.py | 16 ++++++++++++++++ gns3server/controller/udp_link.py | 21 ++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 989619940..b7e15db3b 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -765,6 +765,22 @@ class Project: "Dropping invalid filters on link %s: %s", link_data.get("link_id"), e ) + # Restore traffic-insight markers. Each marker's capture side is resolved + # when the link is (re)created and the marker is applied to uBridge via + # _ubridge_apply_markers in add_ubridge_udp_connection. + for name, marker in (link_data.get("markers") or {}).items(): + try: + await link.start_marker( + name=name, + bpf=marker["bpf"], + tag=marker.get("tag"), + color=marker.get("color"), + ) + except (ControllerError, KeyError) as e: + log.warning( + "Dropping marker %s on link %s: %s", + name, link_data.get("link_id"), e + ) if "link_style" in link_data: await link.update_link_style(link_data["link_style"]) if "show_filters_icon" in link_data: diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index 0ef0ea9ac..a0b28b79d 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -355,15 +355,22 @@ class UDPLink(Link): raise ControllerError(f"Invalid BPF expression: {result.get('error', 'unknown error')}") marker_side = self._choose_marker_side() - data = {"name": name, "bpf": bpf, "tag": tag, "link_id": self._id} - await marker_side["node"].post( - "/adapters/{adapter_number}/ports/{port_number}/markers/start".format( - adapter_number=marker_side["adapter_number"], port_number=marker_side["port_number"] - ), - data=data, - ) + # Record state + runtime capture-side ref unconditionally (so stop/update + # work and the marker is persisted), but only push to uBridge when the + # link is already live. During project load the link is not yet created + # (self._created is False); the marker then rides the NIO via create() + # and is applied once by _ubridge_apply_markers — mirroring exactly how + # update_filters guards its update() call. self._store_capture_node_for_marker(name, marker_side) self._markers[name].update({"bpf": bpf, "tag": tag, "enabled": True, "color": color}) + if self._created: + data = {"name": name, "bpf": bpf, "tag": tag, "link_id": self._id} + await marker_side["node"].post( + "/adapters/{adapter_number}/ports/{port_number}/markers/start".format( + adapter_number=marker_side["adapter_number"], port_number=marker_side["port_number"] + ), + data=data, + ) self._project.emit_notification("link.updated", self.asdict()) self._project.dump()