mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 22:11:14 +02:00
Merge pull request #2816 from yueguobin/feature/traffic-insight-markers
feat: add traffic-insight markers (ubridge mark filter integration)
This commit is contained in:
44 files changed
+2432
-8
No files matched your search
@@ -27,7 +27,7 @@ from fastapi import APIRouter, Depends, Request, status, WebSocket
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from typing import List, Union
|
||||
from uuid import UUID
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import ControllerError
|
||||
@@ -424,6 +424,97 @@ async def web_wireshark_websocket(
|
||||
pass
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/markers",
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))]
|
||||
)
|
||||
async def get_markers(link: Link = Depends(dep_link)) -> dict:
|
||||
"""
|
||||
Return all traffic-insight markers configured on this link.
|
||||
|
||||
Required privilege: Link.Audit
|
||||
"""
|
||||
|
||||
return link.markers
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{link_id}/markers",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def create_marker(
|
||||
marker_data: schemas.MarkerCreate,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> dict:
|
||||
"""
|
||||
Attach a traffic-insight marker to the link.
|
||||
On BPF match uBridge emits MARK signals and appends packets to a pcap.
|
||||
|
||||
Required privilege: Link.Modify
|
||||
"""
|
||||
|
||||
# Auto-generate a link-unique name when the caller omits one. The short
|
||||
# uuid suffix avoids the collision that `marker-{link.id[:8]}` alone would
|
||||
# cause on the second anonymous marker on the same link (start_marker
|
||||
# rejects duplicate names).
|
||||
if marker_data.name and marker_data.name.lower().startswith("global"):
|
||||
raise ControllerError('Names starting with "global" are reserved for inherited markers')
|
||||
name = marker_data.name or f"marker-{link.id[:8]}-{uuid4().hex[:4]}"
|
||||
await link.start_marker(
|
||||
name=name,
|
||||
bpf=marker_data.bpf,
|
||||
tag=marker_data.tag,
|
||||
color=marker_data.color,
|
||||
highlight_duration=marker_data.highlight_duration,
|
||||
)
|
||||
return link.markers.get(name, {})
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{link_id}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def delete_marker(
|
||||
marker_name: str,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> None:
|
||||
"""
|
||||
Remove a traffic-insight marker from the link.
|
||||
|
||||
Required privilege: Link.Modify
|
||||
"""
|
||||
|
||||
await link.stop_marker(marker_name)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{link_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def update_marker(
|
||||
marker_name: str,
|
||||
marker_data: schemas.MarkerCreate,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> dict:
|
||||
"""
|
||||
Update a traffic-insight marker (change BPF, tag, or enabled).
|
||||
|
||||
Required privilege: Link.Modify
|
||||
"""
|
||||
|
||||
await link.update_marker(
|
||||
name=marker_name,
|
||||
bpf=marker_data.bpf if marker_data.bpf else None,
|
||||
tag=marker_data.tag,
|
||||
color=marker_data.color,
|
||||
enabled=marker_data.enabled,
|
||||
highlight_duration=marker_data.highlight_duration,
|
||||
)
|
||||
return link.markers.get(marker_name, {})
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/iface",
|
||||
response_model=Union[schemas.UDPPortInfo, schemas.EthernetPortInfo],
|
||||
|
||||
@@ -203,6 +203,119 @@ 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
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Project-level marker definitions (global rules inherited by every link)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get(
|
||||
"/{project_id}/marker-definitions",
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))]
|
||||
)
|
||||
def get_marker_definitions(project: Project = Depends(dep_project)) -> dict:
|
||||
"""
|
||||
Return all project-level marker definitions with their bound link IDs.
|
||||
|
||||
Required privilege: Project.Audit
|
||||
"""
|
||||
|
||||
result = {}
|
||||
for name, d in project.marker_definitions.items():
|
||||
# Collect which links currently carry an inherited copy.
|
||||
bound = [
|
||||
lid for lid, link in project.links.items()
|
||||
if f"global-{name}" in link.markers
|
||||
and link.markers[f"global-{name}"].get("inherited_from") == name
|
||||
]
|
||||
result[name] = {**d, "link_ids": bound}
|
||||
return result
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{project_id}/marker-definitions",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
)
|
||||
async def create_marker_definition(
|
||||
def_data: schemas.MarkerDefinitionCreate,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> dict:
|
||||
"""
|
||||
Create a project-level marker definition and fan out to every link.
|
||||
|
||||
Required privilege: Project.Modify
|
||||
"""
|
||||
|
||||
if def_data.name and def_data.name.lower().startswith("global"):
|
||||
raise ControllerError('Names starting with "global" are reserved for inherited markers')
|
||||
name = def_data.name or f"def-{project.id[:8]}"
|
||||
await project.create_marker_definition(
|
||||
name=name,
|
||||
bpf=def_data.bpf,
|
||||
tag=def_data.tag,
|
||||
color=def_data.color,
|
||||
highlight_duration=def_data.highlight_duration,
|
||||
)
|
||||
return project.marker_definitions.get(name, {})
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{project_id}/marker-definitions/{def_name}",
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
)
|
||||
async def update_marker_definition(
|
||||
def_name: str,
|
||||
def_data: schemas.MarkerDefinitionCreate,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> dict:
|
||||
"""
|
||||
Update a marker definition and sync all inherited copies on every link.
|
||||
|
||||
Required privilege: Project.Modify
|
||||
"""
|
||||
|
||||
await project.update_marker_definition(
|
||||
name=def_name,
|
||||
bpf=def_data.bpf if def_data.bpf else None,
|
||||
tag=def_data.tag,
|
||||
color=def_data.color,
|
||||
highlight_duration=def_data.highlight_duration,
|
||||
)
|
||||
return project.marker_definitions.get(def_name, {})
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{project_id}/marker-definitions/{def_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
)
|
||||
async def delete_marker_definition(
|
||||
def_name: str,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker definition and remove all inherited copies from every link.
|
||||
|
||||
Required privilege: Project.Modify
|
||||
"""
|
||||
|
||||
await project.delete_marker_definition(def_name)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
|
||||
Reference in new issue
Block a user