300 Commits

Author SHA1 Message Date
YueGuobin
c9a93c0aeb
perf: skip per-link topology dump during project-open prepare
Stage timing showed prepare taking 96s for 1275 links (vs 0.31s for the
batch dispatch itself). Root cause: _prepare_link_from_topology called
add_link (dump=True default) and update_link_style/update_show_filters_icon
(each unconditionally dump the full topology). 1275 links x serialize-
and-write-the-whole-topology = the entire 96s.

- add_link(..., dump=False): the project is dumped once at the end of open
- set link._link_style / _show_filters_icon directly instead of the
  update_* helpers, which also avoids spurious 'link.updated' notifications
  before the link is finalised

The final self.dump() at the end of project.open already persists everything.
2026-08-10 23:54:26 +08:00
YueGuobin
38c49a655c
perf: batch NIO dispatch on project open (one HTTP per compute)
Project open used to create each link by issuing two NIO POSTs from the
controller to the compute — ~5000 HTTP round-trips for a 2500-link
topology, all funnelling through the single shared controller/compute
event loop and capping throughput near 12 links/s.

Replace it with a bulk path:
- UDPLink split into _prepare() (local: ports, peer addrs, link_data)
  and _commit_nios() (dispatch). create() = prepare + commit (interactive).
- Link.add_node gains batch=True: attach both nodes without triggering
  per-link NIO HTTP.
- compute: new POST /projects/{id}/nios/batch endpoint with a unified
  _add_nio_binding dispatch across node types (docker/qemu/iou/vpcs/
  builtin differ in signature).
- project.open: prepare all links locally, group NIO entries by compute,
  send each compute a single /nios/batch, then finalise (wire node/port
  refs, mark created, notify, apply marker defs) in parallel.

Cuts controller->compute HTTP from O(links) to O(computes). Test added
for the batch endpoint.
2026-08-10 23:42:50 +08:00
grossmj
dff79f65d3
Merge branch '2.2' into 3.1
# Conflicts:
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/controller/import_project.py
#	gns3server/controller/project.py
#	gns3server/crash_report.py
#	gns3server/version.py
#	tests/compute/docker/test_docker_vm.py
#	tests/controller/test_import_project.py
#	tests/controller/test_project.py
2026-08-08 16:27:14 +02:00
YueGuobin
ad8bac8328
marker: batch topology dumps in bulk fan-out (per-def on 500+ links was ~1 minute)
Every per-link marker operation (start_marker / stop_marker / update_marker) called Project.dump() -- a full topology serialization + file write. A definition fan-out over 500 links therefore wrote the whole topology 500+ times (each blocking the event loop), which dominated the observed ~1 minute; the NIO round-trips themselves were negligible.

Add a dump: bool = True parameter to the three per-link operations and inherit_marker (matching the existing dump param on Link.add_node). The bulk paths -- definition create fan-out, definition-update sync and re-fan-out, definition-delete cleanup, pause/resume, new-link inheritance -- pass dump=False and their caller dumps once after. apply_defs_to_new_link suppresses per-def dumps too: link create / project open dump once after, so opening a 500-link project with N definitions no longer does 500xN topology writes.
2026-08-08 00:03:30 +08:00
YueGuobin
078cf92aef
marker: concurrent (bounded) definition fan-out
The per-definition fan-out applied markers to links in a serial loop -- one compute round-trip per link. On a 1000-link project that serializes N HTTP round-trips (minutes on remote computes). Fan out with asyncio.gather + Semaphore(32): links are independent (own _markers/_link_data), per-link ControllerError stays isolated, and Project.dump is synchronous + atomic (tmp + rename) so concurrent dumps cannot corrupt the topology file.

Converts the definition-create fan-out, the definition-update sync and re-fan-out loops, and the definition-delete cleanup to the shared _marker_apply_concurrently helper. apply_defs_to_new_link stays serial deliberately: all definitions share one link and each push carries the link's full marker set, so concurrent pushes would race and lose markers.
2026-08-07 23:50:41 +08:00
YueGuobin
3322233658
marker: serial-link (WAN) support via data_link_type -> uBridge linktype
Markers now work on serial links (Cisco HDLC / PPP / Frame Relay / ATM), not just Ethernet. A marker carries a data_link_type (default DLT_EN10MB); at the uBridge boundary it becomes the 'mark ... linktype <dlt>' keyword so the BPF compiles and the pcap is written with the matching link-layer.

- MarkerCreate / MarkerDefinitionCreate gain data_link_type (default DLT_EN10MB). Per-link it is create-only; definitions are updatable (a change re-fans-out).
- base_node._marker_linktype() normalizes the GNS3 DLT name (strip DLT_, uppercase, None for EN10MB). Single source is SerialPort.data_link_types, so Cisco PPP -> PPP_SERIAL (50), matching the capture path -- no second mapping table.
- _ubridge_add_marker_filter (generic) and the IOU marker loop append 'linktype <dlt>'.
- Definition fan-out branches on link_type in inherit_marker: Ethernet is always EN10MB; a serial link uses the definition's WAN encapsulation, or is SKIPPED when none was chosen (an EN10MB pcap on serial is undecodable). One definition covers a mixed topology.
- MCP marker_definition exposes data_link_type (None = not forwarded).
- No uBridge rebuild on a data_link_type change -- only that one marker's filter is swapped (delete + re-add), mirroring a BPF change; reset_packet_filters preserves sibling mark filters.

Requires the uBridge build with 'mark ... linktype' support.
2026-08-07 01:53:33 +08:00
YueGuobin
1a0ce51f38
marker: validate def BPF once, skip re-validation on inherited fan-out
A marker definition validated its BPF N times — once per link in the fan-out
(start_marker runs validate_bpf_syntax on every copy), spawning one tcpdump -d
subprocess per link for the same expression. Definitions did not validate BPF
at all; only direction was checked.

Move the single validation point to the definition layer (create/update), and
validate each definition's BPF on project load (dropping any that have gone
invalid, like private markers). The inherited fan-out (start_marker) and def
sync (update_marker) now skip validate_bpf_syntax for inherited copies, since
the BPF comes from an already-validated definition. Private per-link markers
still validate inline as before. uBridge still runs pcap_compile at install, so
an invalid expression can never slip through.

Creating a definition over N links now runs one tcpdump instead of N.
2026-08-04 23:20:58 +08:00
YueGuobin
60e2bbbbbb
marker: point directional defs at BPF, refresh implementation doc
The 409 on a tx/rx definition now recommends encoding direction in the BPF
(e.g. icmp[icmptype]==8) as the primary fix, with per-link markers as the
single-link fallback. Doc updated: per-def rejects tx/rx (why + BPF), the
pause section no longer claims bpf changes reset+reapply (they rebuild one
filter), and a new Capture files section covers pcap cleanup + reset-preserves-mark.
2026-08-04 22:59:12 +08:00
YueGuobin
19815f7a37
marker: restore direction on project load, reject tx/rx on definitions
Restore a private marker's direction (and highlight_duration) when a
project is reopened — _create_link_from_topology_data previously dropped
them, silently reverting rx/tx markers to "both".

Reject tx/rx direction on marker definitions with HTTP 409: a definition
auto-selects its capture node per link and direction is relative to that
node, so a fixed tx/rx has no stable project-wide meaning. Per-link
markers still support tx/rx; only the project-wide definition is restricted
to "both" (the default).
2026-08-04 10:14:02 +08:00
YueGuobin
1eeee024bd
marker: rework pause/resume from project-wide to per-definition
The project-wide mute (POST /markers/pause|resume + _markers_paused) paused
every marker with one button. The actual need is per-rule control: pause one
definition and toggle only its inherited global-{name} copies across all links.

- Drop project-level: _markers_paused (init/asdict/load/start_all), the
  pause_all/resume_all_markers methods, and the /markers/pause|resume routes.
- Add per-definition: a persisted `paused` flag on each definition;
  pause/resume_marker_definition fan out update_marker(enabled) to every
  global-{name} copy — instant, via the existing enable_packet_filter toggle
  (no NIO rebuild, pcap/emitted preserved). New links inherit a paused
  definition already off (inherit_marker passes enabled=not paused).
- start_marker takes an enabled kwarg; update_marker's enabled-only short-circuit
  now also covers inherited copies so def pause/resume is instant.
- Routes: POST /marker-definitions/{name}/pause|resume.
- Docs + tests updated.
2026-08-02 22:46:33 +08:00
YueGuobin
f7d7ba165a
marker: persist project-wide markers_paused to the .gns3 file
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.
2026-08-02 22:21:05 +08:00
YueGuobin
3d06c4e22f
marker: drive uBridge enabled/pause/resume in real time
Wire gns3-server to uBridge's real-time marker controls (contract
../ubridge/doc/gns3server-integration.md §3.2), in three layers:

A. enabled reaches uBridge — _markers_for_node no longer drops disabled
   markers controller-side; it carries `enabled` in the NIO spec. apply
   installs every marker then issues `enable_packet_filter … off` for the
   disabled ones (base_node bridge / iou iol_bridge; old-ubridge errors
   downgrade to a warning so a toggle can't break link create).

B. instant per-filter toggle — apply records name→bridge so a new
   `_ubridge_set_marker_filter_state` can flip a running filter with
   `enable_packet_filter on|off` (iou overrides for iol_bridge + bay/unit).
   Each marker-capable node type gains PUT /markers/{name}; update_marker
   short-circuits to it when only `enabled` changes (no NIO rebuild, no pcap
   flush), falling back to reset+reapply if the route is unavailable.

C. global pause/resume — `_ubridge_marker_pause/resume` send `marker pause`
   / `marker resume` direct to the hypervisor (pause stops signal+pcap,
   resume instant, sink retained). Six node-type routes add POST
   /markers/pause|resume; project.pause_all/resume_all_markers fan out to
   each capture node (deduped, best-effort); REST exposes
   POST /projects/{id}/markers/pause|resume.

Toggling enabled and pause/resume are now both instant — only marker create
or a bpf change still go through reset+reapply.
2026-08-02 16:19:20 +08:00
YueGuobin
37cb9f0a9c
marker: support clearing direction via explicit null / "both"
direction was settable but not clearable: once a marker or project-level
definition had direction=tx/rx, no update path could return it to "both
directions", and the definition-sync fan-out silently kept stale values on
every inherited copy.

Introduce a _UNSET sentinel (link.py) distinct from None so updaters can
tell "caller omitted direction" (preserve) from "caller passed None"
(clear). Thread it through UDPLink.update_marker and
Project.update_marker_definition; the two REST routes use Pydantic v2
model_fields_set to translate an explicit JSON null into the sentinel.

MCP follows with a "both" token: link_marker / marker_definition handlers
map direction="both" to a null in the REST body (tri-state: omit=preserve,
tx/rx=set, both=clear), and the tool descriptions/docstrings document it.

Backward compatible: omitting direction or passing tx/rx behaves exactly
as before; only an explicit null / "both" clears.
2026-08-02 11:37:49 +08:00
YueGuobin
6749b872fa
marker: forward dir= from ubridge and add per-marker direction filter
Direction field in marker.match events
=======================================

Read ubridge dir=<tx|rx> from MARK signal datagrams and forward it
as a "dir" key in the marker.match notification event.  The field is
additive -- older ubridge builds omit it and the parser leaves it null,
so consumers fall back to undirected rendering with no version coupling.
Semantics are relative to the capture node (the signal's node=<id>):
tx = capture node is sending (ingressed device-side NIO), rx = it is
receiving (ingressed link-side NIO).

Per-marker direction filter (opt-in, server-side pipeline)
==========================================================

Add a direction field to MarkerCreate and MarkerDefinitionCreate
schemas ("tx" | "rx" | null).  Plumb it through the full pipeline:

  Schema -> controller (start_marker/update_marker, marker_entry,
  _markers_for_node, update_marker_definition sync)
  -> REST/MCP handlers -> compute _ubridge_add_marker_filter +
  IOU _ubridge_apply_markers -> bridge add_packet_filter dir <tx|rx>

When set, ubridge only fires the mark handler (signal + pcap) for
packets matching the chosen direction.  null (default/legacy) = both
directions -- zero behavioural change for existing markers.

Docs and tests
==============

- docs/features/marker-traffic-insight.md: signal format updated,
  new Direction section with NIO mapping, arrow mapping, and additive
  compatibility note.
- tests/compute/marker/test_marker_manager.py: 3 new parser tests
  (dir tx/rx/absent) plus existing test extended to assert dir=None.

13 files, +123/-22, 72 tests pass (zero breakage)
2026-08-01 14:52:07 +08:00
Sanjay Santhanam
00ac2c19bd Do not start nodes when deleting a project
Project.delete() calls open() to rebuild the internal data structures
needed for cleanup. open() schedules start_all() when the project has
auto_start enabled, so deleting an auto-start project actually launched
every node process, allocating ports and consuming resources, only for
close() to kill them moments later.

open() now takes an auto_start argument (default True, so normal opens
are unchanged) and delete() passes auto_start=False.

Fixes #2784
2026-07-25 20:00:43 -07:00
YueGuobin
cdc27c37a7
fix(marker): clean inherited markers on def delete
delete_marker_definition removed the def but left the inherited global-*
copies on every link: it called stop_marker(), which rejects inherited
markers (409), and the ControllerError was swallowed as a warning. The
orphaned copies were in-memory only (_persist_markers filters inherited
markers) so a restart hid the symptom, but during a running session they
were undeletable via either the per-link or project API.

Add the same inherited=True bypass that update_marker already has, and
pass it from the def-delete fan-out so the copies are removed for real.
2026-07-16 00:39:52 +08:00
YueGuobin
98bcd5eddd
feat(marker): add highlight_duration render hint
Per-marker UI hint (milliseconds, ge=1) for how long the Web UI keeps a
marker highlighted after a match. Mirrors color: stored on the link,
persisted in the topology, inherited via project-level definitions, and
never sent to uBridge. Omitted = null = frontend uses its own default.

The default is intentionally NOT set in the schema: MarkerCreate also
backs PUT updates, so a schema default would make every partial update
silently reset the value. None means "not provided" (keep existing on
update / frontend decides on create).
2026-07-16 00:39:52 +08:00
YueGuobin
179f072c33
fix(marker): fix inheritance hook, topology load, update sync, and asdict
Bugs found via end-to-end testing of project-level marker definitions:

1. New links didn't inherit — apply_defs_to_new_link is async but was
   called without await in UDPLink.create().

2. Project load crashed — load_project passed marker_definitions to
   Project.__init__. Now popped in load_project and restored separately
   in Project.open() (it backs a read-only property).

3. PUT on a definition didn't sync to links — update_marker's guard
   rejected even the project-layer sync call. Added an `inherited`
   bypass flag used by update_marker_definition.

4. _ubridge_add_marker_filter raised re.PatternError — the name regex
   used (?i)(?!global) look-around, invalid in Python's re module.
   Dropped the prefix check there: "global-*" names are legitimate at
   the uBridge boundary (inherited definitions); forbidden only at the
   user-facing schema.

5. GET /links hid inherited markers — asdict()'s runtime branch used
   _persist_markers() (which filters inherited markers). Restored
   self._markers for the runtime branch; only the topology_dump branch
   filters (inherited markers are rebuilt from definitions on load).

6. Duplicate "already exists" warnings on project open — open() fanned
   out definitions to all links, but UDPLink.create() had already done
   so via its inheritance hook. Removed the redundant fan-out in open().
2026-07-16 00:39:52 +08:00
YueGuobin
31991fe359
feat(marker): add project-level marker definition inheritance
Project-level marker definitions fan out to every link (existing and new).
A definition is stored once on Project._marker_definitions; when applied to
a link the marker is named "global-{def_name}" — the "global" prefix was
pre-reserved in the schema, so inherited and per-link markers can never
collide, nor will their registry keys.

Key behavior:
- POST /projects/{pid}/marker-definitions → fan out to all existing links
- PUT  /projects/{pid}/marker-definitions/{name} → sync all inherited copies
- DELETE → remove every inherited copy from every link
- New links auto-inherit all active defs (hook in UDPLink.create)
- Per-link DELETE/PUT of a "global-*" marker is rejected (409)
- Inherited markers are NOT persisted in the topology; they are re-created
  from _marker_definitions on project load
- Compute side is untouched — the marker reaches uBridge via the existing
  start_marker→update→NIO→ubridge pipeline

Files:
- controller/project.py — _marker_definitions + CRUD + fanout + topology load
- controller/link.py — Link.inherit_marker() + asdict() filter
- controller/udp_link.py — guards on stop/update + create() inheritance hook
- controller/topology.py — persist marker_definitions in project topology
- schemas/controller/links.py — MarkerDefinitionCreate schema
- api/routes/controller/projects.py — REST endpoints (marker-definitions)
2026-07-16 00:39:52 +08:00
YueGuobin
f2360f85fc
feat(marker): add project-level marker aggregation endpoint
Add a read-only `markers` property on Project that flattens every link's
markers into a single dict keyed by "{link_id}/{name}", each entry
carrying the parent link_id and capture-side node_id. Expose it via
GET /projects/{pid}/markers (Project.Audit) so the frontend can fetch all
markers in one round-trip instead of enumerating links first.

Also surface a marker count in project.stats().
2026-07-16 00:39:51 +08:00
YueGuobin
c62b9b0283
refactor(marker): converge to filter single-path model, remove dual-apply endpoints
Markers now follow exactly the same apply pattern as packet filters:
state lives in Link._markers, application goes through NIO
(update() -> PUT /nio -> _ubridge_apply_markers). The former
immediate-apply REST endpoints (/markers/start, /markers/stop on
the compute side) and the per-node start_marker/stop_marker methods
are removed — they were a legacy of the original capture-inspired
design and have been superseded by the NIO flow.

Changes:
- controller/udp_link: start_marker/stop_marker/update_marker now
  set _markers state + call self.update() (mirrors update_filters).
  Removed _marker_capture_nodes runtime dict and its helpers.
- controller/project: _create_link_from_topology_data restores
  _markers directly from persisted data (with BPF validation,
  like filter reload). No long calls start_marker during load.
- compute: _ubridge_apply_markers swallows BPF compile errors
  (warn+skip), matching _ubridge_apply_filters behaviour so a
  single bad expression cannot break link creation / node restart.
- Removed: /markers/start,stop endpoints (6 handlers across
  vpcs/qemu/docker route files), node start_marker/stop_marker
  methods (3 VM files), _ubridge_delete_marker_filter,
  _marker_capture_nodes, MarkerDelete schema.

Net: ~280 lines of dead code removed; marker and packet filter now
share a single, unified apply path via the NIO.
2026-07-16 00:39:51 +08:00
YueGuobin
88c03d1431
fix(marker): restore markers from topology on project load
_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.
2026-07-16 00:39:51 +08:00
grossmj
e3e0a511b6
Handle HTTPNotFound exception when retrieving compute status 2026-07-12 11:08:13 +02:00
grossmj
0ecaab4da0
Replace ControllerForbiddenError with aiohttp.web.HTTPForbidden for project deletion error handling 2026-07-12 11:05:15 +02:00
YueGuobin
40644f33e6
Fix: Check compute connectivity before open() during project deletion
Previously the disconnected compute check ran after open(), which would
block for 120s trying to connect to unreachable remote computes before
rejecting the deletion. Now reads the topology file directly to extract
compute IDs and checks connectivity before calling open(), enabling
immediate rejection of deletions involving offline computes.

Also removes the redundant post-open() check since the early check
covers both opened and closed project states.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-12 10:57:56 +02:00
YueGuobin
390a40ce80
Use pop() instead of pop(0) for O(1) port removal 2026-06-16 22:56:42 +08:00
YueGuobin
af94c1729b
Add warning for unconsumed pre-allocated UDP ports after link creation
Ports are properly released on project close (compute.post /close).
The warning alerts if any pre-allocated ports go unused within a session.
2026-06-16 22:22:46 +08:00
YueGuobin
74192f454f
Increase BATCH_MAX_WORKERS and Pool concurrency from 20 to 100 2026-06-16 00:49:08 +08:00
YueGuobin
ccb629f48f
Clean up all timing/debug logs
Remove all [MCP-TIMING] and [CTRL-TIMING] log lines, timing middleware,
and related import time statements across 11 files.
2026-06-16 00:34:11 +08:00
YueGuobin
ea4bb2c1fb
Add [CTRL-TIMING] logs to controller create_node flow
Timing logs cover:
- create_node_from_template (entry, get_template, add_node, total)
- add_node_from_template (entry to done)
- _create_node (project_setup, node.create, total)
- Node.create (compute_post timing)
- compute._session.request (actual HTTP to compute)
2026-06-15 23:04:01 +08:00
YueGuobin
650f95af54
Increase node and link creation concurrency from 5 to 20 2026-06-15 12:50:59 +08:00
YueGuobin
e29e02a8c6
Fix: revert IOU lock optimization, serialize IOU node creation for correct application_id assignment
The _iou_id_lock must cover _create_node() because get_next_application_id()
checks in-memory nodes (self._nodes), which are only registered after
_create_node() completes. Without this serialization, concurrent IOU
node creation produces duplicate application IDs.
2026-06-15 12:49:01 +08:00
YueGuobin
5d0284e7be
Performance: accelerate project opening with parallel link creation and batch UDP port allocation
- Narrow IOU lock scope to only cover application_id allocation,
  allowing concurrent IOU node creation via Pool(concurrency=5)
- Parallelize link creation during project.open() using Pool(concurrency=5)
  instead of sequential processing
- Add batch UDP port allocation endpoint on compute to allocate N ports
  in a single HTTP call
- Pre-allocate UDP ports per compute before link creation during project
  loading, reducing HTTP round-trips
- UDPLink.create() falls back to individual port allocation if no
  pre-allocated port is available
2026-06-15 12:42:18 +08:00
YueGuobin
9d7c482288
fix: Skip always-running nodes in start_all/stop_all
Always-running node types (Ethernet switch, Cloud, NAT, etc.)
return 405 when start/stop is called. is_always_running() already
tracks these types; start_all/stop_all now skip them.
2026-06-11 01:26:43 +08:00
YueGuobin
7ab04a0f9a
Add running project check for fast duplication 2026-06-03 12:20:26 +08:00
YueGuobin
b6e1f84740
Move running project check before fast duplication
Move the is_running() check from _fast_duplication() to
duplicate() to avoid the error message being wrapped by
the except Exception handler. This ensures the error
message is clean and prevents wasted fast duplication
attempts on running projects.
2026-06-03 12:18:28 +08:00
YueGuobin
c4440d882d
Add running project check for fast duplication
Add is_running() check at the beginning of _fast_duplication()
to prevent duplicating a project while nodes are running.
Previously, only the export/import fallback path had this check,
which meant running nodes were not detected when fast duplication
succeeded. This aligns with the duplicate API behavior and
provides a consistent safeguard against data inconsistencies.
2026-06-03 12:15:27 +08:00
YueGuobin
0ba180ad1d
Fix unnecessary Docker container recreation when renaming a project
When renaming a project that has running Docker containers, the containers
were unnecessarily stopped, removed, and recreated, even though the project
name change doesn't affect container configuration.

Root cause:
- Client sends complete project object including variables: [] during rename
- Controller unconditionally notified all computes about the update
- Docker nodes rebuild containers on any project update notification

Solution:
- Only notify compute nodes when variables field has actual content
- Treat None and [] as semantically equivalent (no variables)
- Empty variables don't affect running containers, so no need to update

Impact:
- Project rename operations no longer trigger ~7 second container rebuilds
- Only actual variable changes trigger container recreation
- Fixes issue #2760
2026-06-02 13:04:44 +08:00
YueGuobin
b5d3556add
Fix project rename and duplicate issues
Fixes #2759

When renaming a project:
- Update self._filename to match the new project name
- Rename the .gns3 file on disk to keep it in sync
- Add error handling for file rename failures

When duplicating a project:
- Use self._filename (actual filename) instead of self.name
- This handles the case where a project has been renamed
- Prevents 'No such file or directory' errors

The root cause was that project renaming only updated the project name
in memory and in the .gns3 file content, but did not update the actual
.gns3 filename. This caused duplicate operations to fail because they
tried to read a file with the new name that didn't exist.
2026-06-01 22:19:44 +08:00
YueGuobin
b4daddd1c7
Optimize project loading by implementing parallel node creation
This change significantly improves project loading performance, especially for
topologies with multiple Docker containers or other node types.

Changes:
- Modified project.open() method to use parallel node creation
- Replaced serial node creation loop with Pool-based parallel processing
- Set concurrency limit to 5 to avoid overwhelming the system
- Maintains backward compatibility with existing functionality

Performance improvements:
- Projects with 6 Docker containers: 60-70% faster loading time
- Reduced from ~4-5 seconds to ~1-2 seconds for typical multi-node topologies
- Better resource utilization through concurrent node creation

Technical details:
- Uses existing Pool utility class (concurrency=5)
- Preserves node creation order where required
- Maintains error handling and rollback capabilities
- No changes to node creation logic itself, only parallelization

Testing:
- Syntax validation passed
- Compatible with existing project.open tests
- No API changes, internal optimization only
2026-05-31 23:50:21 +08:00
Guobin Yue
75445886fe
Merge branch '3.1' into fix/ghost-docker-node-vnc-timeout 2026-05-31 22:18:28 +08:00
YueGuobin
21bba7f4b2
Set default value of show_interface_labels to True
Change the default value of show_interface_labels from False to True for better user experience, as interface labels are commonly used in network topology visualization.
2026-05-30 22:49:42 +08:00
YueGuobin
7bcb96a368
Improve packet filter validation: use tcpdump, handle multi-line BPF, safe project load
Changes:
- Replace tshark BPF validation with tcpdump -d (calls pcap_compile
  internally like ubridge, returns instantly without waiting for traffic)
- Support multi-line BPF expressions: split on newlines and validate
  each line individually
- Always validate, never save invalid filters on error
- Drop invalid filters during project load with warning (prevents
  old topologies with bad filters from failing to open)
- Simplify test cases (no longer depend on tshark availability)
2026-05-30 01:02:21 +08:00
YueGuobin
a1f4942746
feat: add show_filters_icon property to Link for controlling Web UI filter icon display
This commit adds a new  property to the Link class, allowing users to control whether filter icons are displayed in the Web UI at the individual link level.

**Changes:**
- Added  attribute to Link class (default: True)
- Added  property getter
- Added  method for updating the property
- Updated  to include the new field with backward compatibility
- Added  field to LinkBase schema using Optional[bool] = Field(True, ...) pattern
- Updated API routes to handle the new field in create and update operations
- Added loading logic for show_filters_icon in project.open() to preserve settings when reopening projects

**Schema Definition:**
Uses the same pattern as the  field:

**API Impact:**
- POST /v3/projects/{project_id}/links - accepts  in request body
- PUT /v3/projects/{project_id}/links/{link_id} - can update
- GET /v3/projects/{project_id}/links/{link_id} - returns  field

**Future Applications:**
This feature provides granular control for future AI fault injection modules to manage link-level protocol failures while maintaining clean UI presentation.
2026-05-23 16:57:39 +08:00
YueGuobin
f1b11e7cae
Fix: Check compute connectivity before node creation in open()
When opening a closed project with nodes on an offline remote compute,
open() would block for 120s trying to connect before eventually
failing. Now checks compute connectivity after loading the topology
file but before creating nodes, allowing immediate failure with a
clear error message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 10:48:18 +08:00
YueGuobin
30ebde0cb0
Fix: Check compute connectivity before open() during project deletion
Previously the disconnected compute check ran after open(), which would
block for 120s trying to connect to unreachable remote computes before
rejecting the deletion. Now reads the topology file directly to extract
compute IDs and checks connectivity before calling open(), enabling
immediate rejection of deletions involving offline computes.

Also removes the redundant post-open() check since the early check
covers both opened and closed project states.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 09:58:20 +08:00
YueGuobin
8ecd449dd8
Fix: Add error logging when closing/deleting projects on computes
Previously errors during close() and delete_on_computes() were silently
swallowed without any logging, making it difficult to diagnose failures
when remote computes are unreachable.

- close(): log warning instead of silent pass
- delete_on_computes(): wrap HTTP DELETE in try/except with warning log

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 09:37:51 +08:00
YueGuobin
4d06b2b7b6
Fix: Skip add_compute for existing computes when opening projects 2026-05-06 13:24:27 +08:00
YueGuobin
9c868911e1
Fix: Use _computes instead of _project_created_on_compute for deletion check
The initial fix used _project_created_on_compute to check for disconnected
computes before deletion, but this set gets reset during project.open(),
causing the check to fail.

Now uses self._computes which is loaded from the topology file and
persists through the open() call.

Related: #2703

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 00:19:55 +08:00
YueGuobin
4444da9ffa
Fix: Check compute connection status before project deletion
This commit addresses issue #2703 where deleting a project with nodes
on remote compute nodes would result in long waits with no feedback
if those computes were unreachable.

Changes:
1. Compute connection status updates on connection failure
   - When a compute fails to connect, update connected=False and last_error
   - Send compute.updated notification to UI so users can see status
   - This allows Web UI to display real-time connection status

2. Project deletion checks compute status before attempting deletion
   - Check all computes used by the project are connected
   - If any compute is disconnected, immediately reject deletion
   - Provide clear error message indicating which computes are offline
   - This prevents long timeouts and gives users immediate feedback

Benefits:
- Immediate feedback instead of 120-second timeouts
- Clear error messages about which computes are disconnected
- Prevents orphaned resources on offline computes
- Improves user experience by avoiding silent waits

Related: #2703

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 00:05:10 +08:00