From f7d7ba165a81f335d3f72caf9d3fe27092e03659 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 2 Aug 2026 22:21:05 +0800 Subject: [PATCH] marker: persist project-wide markers_paused to the .gns3 file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pause/resume was fire-and-forget: the controller sent marker pause/resume but stored nothing, so the Web UI could only keep a local optimistic flag that was lost on panel reopen. Treat the mute as a project-level config (like per-marker enabled): record _markers_paused, persist it in the topology (asdict + load), and echo it on the project object so the UI renders from server truth. Because marker pause is a uBridge runtime flag that resets on node restart, start_all re-applies the mute to freshly started uBridges after a project reopen — a paused project stays paused across close/reopen. --- docs/features/marker-traffic-insight.md | 8 ++++++++ gns3server/controller/project.py | 12 ++++++++++++ tests/controller/test_project.py | 1 + 3 files changed, 21 insertions(+) diff --git a/docs/features/marker-traffic-insight.md b/docs/features/marker-traffic-insight.md index 5e4e51ea8..3287d4d20 100644 --- a/docs/features/marker-traffic-insight.md +++ b/docs/features/marker-traffic-insight.md @@ -172,6 +172,14 @@ The two levers compose and do not overlap: | `marker pause` (project) | stop | stop | kept (resume instant) | | `marker resume` (project) | resume | resume | kept | +The project-wide pause state is **persisted** in the `.gns3` file as +`markers_paused` and echoed on the project object (`GET /v3/projects/{pid}`, +the `asdict()` body), so the Web UI renders the mute button from server truth +rather than a local optimistic flag. Because `marker pause` is a uBridge +runtime flag that resets when a node restarts, `start_all` re-applies the mute +to freshly started uBridges after a project reopen — so a paused project stays +paused across close/reopen. + ## API Endpoints All endpoints require a JWT bearer token (`POST /v3/access/users/authenticate`). The diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 7c68f3eed..371167883 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -214,6 +214,7 @@ class Project: self._nodes = {} self._links = {} self._marker_definitions = {} # name → {bpf, tag, color, highlight_duration} + self._markers_paused = False # project-wide marker mute (persisted, see asdict) self._drawings = {} self._snapshots = {} self._computes = [] @@ -932,6 +933,7 @@ class Project: and a node that is down or running an old compute is skipped. """ + self._markers_paused = True seen = set() for link in list(self._links.values()): for info in link.markers.values(): @@ -943,10 +945,12 @@ class Project: await self.get_node(node_id).post("/markers/pause") except Exception: pass + self.dump() async def resume_all_markers(self): """Resume marker signal+pcap emission on every marker-hosting node.""" + self._markers_paused = False seen = set() for link in list(self._links.values()): for info in link.markers.values(): @@ -958,6 +962,7 @@ class Project: await self.get_node(node_id).post("/markers/resume") except Exception: pass + self.dump() @property def marker_definitions(self): @@ -1472,6 +1477,7 @@ class Project: defs = project_data.get("marker_definitions") if isinstance(defs, dict): self._marker_definitions = defs + self._markers_paused = bool(project_data.get("markers_paused", False)) topology = project_data["topology"] for compute in topology.get("computes", []): @@ -1810,6 +1816,11 @@ class Project: if not node.is_always_running(): pool.append(node.start) await pool.join() + # marker pause is a uBridge runtime flag that resets when a node + # restarts, so re-apply the project-wide mute to the freshly started + # uBridges (markers are installed during node start). + if self._markers_paused: + await self.pause_all_markers() @open_required async def stop_all(self): @@ -1925,6 +1936,7 @@ class Project: "variables": self._variables, "created_by": self._created_by, "marker_definitions": self._marker_definitions, + "markers_paused": self._markers_paused, } def __repr__(self): diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index b0202c01f..570fa7296 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -83,6 +83,7 @@ async def test_json(): "supplier": None, "variables": None, "marker_definitions": {}, + "markers_paused": False, "created_by": None }