diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 333053e3b..39c225594 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -203,6 +203,21 @@ def get_project_stats(project: Project = Depends(dep_project)) -> dict: return project.stats() +@router.get("/{project_id}/markers", dependencies=[Depends(has_privilege("Project.Audit"))]) +def get_project_markers(project: Project = Depends(dep_project)) -> dict: + """ + Return all traffic-insight markers across every link in the project. + + Each entry is keyed ``"{link_id}/{marker_name}"`` and carries the + marker's BPF, tag, color, enabled flag, plus its parent ``link_id`` + and capture-side ``node_id`` for frontend filtering / grouping. + + Required privilege: Project.Audit + """ + + return project.markers + + @router.post( "/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT, diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index c0c0a262a..1c446082a 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -898,6 +898,30 @@ class Project: return self._get_closed_data("links", "link_id") return self._links + @property + def markers(self): + """ + Project-level read-only aggregation of all markers across every link. + + Each entry is keyed ``"{link_id}/{marker_name}"`` so the flat dict is + globally unique within the project. The value is a clone of the link's + per-marker dict plus ``link_id`` and ``node_id`` (the capture-side node) + for convenience — the frontend can filter/group by link or node without + extra round-trips. + + :returns: dict[str, dict] — keyed by "{link_id}/{marker_name}" + """ + result = {} + for link_id, link in self._links.items(): + for name, info in link.markers.items(): + key = f"{link_id}/{name}" + result[key] = { + **info, + "link_id": link_id, + "node_id": info.get("capture_node_id"), + } + return result + @property def snapshots(self): """ @@ -1710,6 +1734,7 @@ class Project: "links": len(self._links), "drawings": len(self._drawings), "snapshots": len(self._snapshots), + "markers": sum(len(link.markers) for link in self._links.values()), } def asdict(self):