Commit Graph
305 Commits
Author SHA1 Message Date
Jeremy Grossmann f184883aa7 Merge branch '3.1' into images-nested-folders-support 2026-10-06 16:50:40 +02:00
Mark Paronyan d9678ce98c refactor: derive update schemas with PartialUpdateModel 2026-10-06 16:46:22 +03:00
Cristi 207db770c6 feat(image-manager): Add support for nested image folders and appliance compatibility checks 2026-10-05 18:49:21 +03:00
Mark Paronyan 306d8fbde4 refactor: remove unused imports 2026-10-03 19:43:58 +03:00
Mark Paronyan 4ebc941951 style: sort imports in controller API routes 2026-10-02 12:43:26 +03:00
Mark Paronyan a77e06b907 Merge branch '3.1' into ruff/trivia-fixups 2026-10-01 19:58:02 +03:00
Mark Paronyan d356a68987 refactor: replace aliased OSError subclasses 2026-10-01 19:57:44 +03:00
Mark Paronyan 7d0be62da8 style: drop redundant utf-8 encode arguments 2026-10-01 19:57:44 +03:00
Mark Paronyan 9e5ab02147 style: remove unused noqa directives 2026-10-01 19:57:40 +03:00
grossmj 1456d36bf0 chore(tests): fix mypy errors 2026-10-01 15:22:17 +02:00
Jeremy Grossmann a2cdc14a25 Merge branch '3.1' into image-manager-out-of-sync 2026-10-01 14:44:24 +02:00
Jeremy Grossmann e90be1ebac Merge pull request #2930 from markparonyan/mypy-db-templates-images
fix(typing): resolve the remaining mypy errors (18 modules)
2026-09-29 21:37:14 +02:00
Mark Paronyan bacfab8465 fix(typing): resolve mypy errors in api.routes.controller.pools 2026-09-29 22:02:21 +03:00
Mark Paronyan adf9c15e21 fix(typing): resolve mypy errors in api.routes.controller.projects 2026-09-29 22:02:21 +03:00
Mark Paronyan 323055c486 fix(typing): resolve mypy errors in api.routes.controller.templates 2026-09-29 22:02:20 +03:00
Mark Paronyan bbf4e6aa2c fix(typing): resolve mypy errors in api.routes.controller.images 2026-09-29 22:02:20 +03:00
Mark Paronyan 4361598df0 fix(typing): resolve mypy errors in api.routes.controller.llm_model_configs 2026-09-29 22:02:19 +03:00
Mark Paronyan b1d13edd2d fix(typing): resolve mypy errors in api.routes.controller.users 2026-09-29 22:02:18 +03:00
Mark Paronyan a3cb73da3d fix(typing): resolve mypy errors in api.routes.controller.dependencies.authentication 2026-09-29 21:48:33 +03:00
Jeremy Grossmann e8f49877fc Merge branch '3.1' into mypy-api-routes-controller-1 2026-09-29 13:28:33 +02:00
Jeremy Grossmann 7f4f9eb2d0 Merge branch '3.1' into mypy-api-routes 2026-09-29 13:25:35 +02:00
Jeremy Grossmann 563b6080f0 Merge pull request #2923 from markparonyan/mypy-api-routes-controller-2
fix(typing): resolve mypy errors in gns3server.api.routes.controller
2026-09-29 13:13:58 +02:00
Jeremy Grossmann 9e9a778f8b Merge pull request #2912 from markparonyan/mypy-api-controller-routes
fix(typing): resolve mypy errors in api.routes.controller
2026-09-29 12:50:06 +02:00
Mark Paronyan ebe6dee916 fix(typing): resolve mypy errors in api.routes.controller.snapshots 2026-09-29 09:23:08 +03:00
Mark Paronyan 68d96193ec fix(typing): resolve mypy errors in api.routes.controller.groups 2026-09-29 09:23:07 +03:00
Mark Paronyan c340b18ba2 fix(typing): resolve mypy errors in api.routes.controller.roles 2026-09-29 09:23:07 +03:00
Mark Paronyan 2cf90dfce6 fix(typing): resolve mypy errors in api.routes.controller.privileges 2026-09-29 09:23:07 +03:00
Mark Paronyan 93419fc33a fix(typing): resolve mypy errors in api.routes.controller.nodes 2026-09-29 09:23:07 +03:00
Mark Paronyan 0872576b46 fix(typing): resolve mypy errors in api.routes.controller.acl 2026-09-29 09:23:06 +03:00
Mark Paronyan a648bf3780 fix(typing): resolve mypy errors in api.routes.controller.dependencies.database 2026-09-29 09:23:06 +03:00
Mark Paronyan 58609bae7b fix(typing): resolve mypy errors in api.routes.controller.controller 2026-09-29 09:23:06 +03:00
Mark Paronyan d826285748 fix(typing): resolve mypy errors in api.routes.controller.computes 2026-09-29 09:23:06 +03:00
Mark Paronyan bbdd9bf861 fix(typing): resolve mypy errors in api.routes.controller.drawings 2026-09-29 08:48:04 +03:00
Mark Paronyan 6526beeb3d fix(typing): resolve mypy errors in api.routes.controller.chat 2026-09-29 08:48:04 +03:00
Mark Paronyan 777258ff68 fix(typing): resolve mypy errors in api.routes.controller.api_keys 2026-09-29 08:48:04 +03:00
Cristi 42d30651da Merge origin/3.1 into image-manager-out-of-sync
Preserve image reconciliation, locking, and regression coverage while integrating upstream release, dependency, typing, and formatting updates. Align feature code with the new Ruff checks.
2026-09-28 23:16:24 +03:00
Mark Paronyan ecff4beb65 fix(typing): resolve mypy errors in api.routes.controller.links 2026-09-28 21:47:46 +03:00
Cristi 046dff4d08 fix(images): reconcile the image inventory with filesystem changes 2026-09-28 16:01:36 +03:00
Mark Paronyan a0450b6bce refactor: ruff autofixes 2026-09-27 15:55:10 +03:00
YueGuobin 5d91ca0efc fix: harden sharkd replay sessions and serve the uncapped frame list
Review-driven session/transport fixes (each reproduced live against
sharkd 4.6.7 before fixing):

- raise the RPC stream limit to 16 MB: a full 1000-row frames page
  measures ~190 KB against the 64 KB StreamReader default, which failed
  the request with a 500 and desynchronized the resident session; a
  line-over-limit ValueError is now treated as a transport failure
- verify JSON-RPC reply ids: a timed-out request's late reply was
  served as the next request's answer; timeouts, dead pipes, malformed
  and stale replies now kill the session for good instead
- make check-spawn atomic under one manager lock: concurrent requests
  for the same pcap double-spawned sharkd and leaked the loser (process
  plus /tmp scratch copy) forever
- refcount sessions and evict idle only (LRU, cap raised 8 -> 16): a
  tag with more sources than the cap respawned every source on every
  request, and concurrent requests could get their session killed
  mid-RPC (spurious 502)
- map FilterError to sharkd's filter rejection (-13002) only; other
  engine failures with a filter set are 502, not a client 400
- detail: accept an optional frame_number to disambiguate
  same-microsecond frames (ts is not unique within a pcap); drop the
  -8003 -> 404 mapping (the range is validated locally, engine errors
  are real faults); a failed hex read is a 404 instead of "hex": null
- a pcap deleted mid-request is a 404, not a 500; the pcap-sized
  scratch copy runs off the event loop; server shutdown kills every
  resident session and drops its scratch directory
- pin the packet-list layout through scratch-HOME Wireshark
  preferences: the column indexes are a contract the server owns
  (protocol-level column negotiation is rejected by sharkd 4.6.x)

Range contract change (WebUI moved to an always-flat list): the merged
frame list is returned in full, deliberately uncapped - truncated and
per-second buckets are removed, frame_count always equals
len(frames), and rendering cost is the client's concern (the window
endpoint remains the incremental path).
2026-09-11 00:55:18 +08:00
YueGuobin ef5d81498a feat: accept link=<link_id> on the replay range and frames endpoints
Narrows the merged frame stream to one capture source BEFORE counting,
slicing and bucketing (frame_count / frames | buckets all recomputed on
the narrowed set), AND-composing with the display filter. A pure
identity filter applied before any engine work — only the selected
link's pcap gets a sharkd pass, so link+filter is cheaper than filter
alone.

Two boundaries by contract with the Web UI:
- sources[] stays the tag's stable inventory: every capture source
  listed with engine-free TOTAL counts, unaffected by link/filter — a
  source dropdown must not shrink when the view narrows (this also
  settles sources[].count on total counts rather than post-filter
  matches, which no spec ever required)
- an unknown link_id matches nothing: frame_count 0, start null, empty
  frames/buckets — the same shape as a zero-match display filter,
  deliberately not a 404; an empty link param is treated as absent
2026-09-09 00:53:06 +08:00
YueGuobin 790c423c26 feat: drive marker replay with resident sharkd sessions
sharkd (the Wireshark daemon) is now the single decode engine — the
tshark/PDML path is gone, and without sharkd every replay endpoint
returns 501 (no degraded mode: one engine, one rendering shape for the
Web UI).

- Frame entries gain packet-list columns from sharkd's frames RPC:
  src/dst/proto/info plus the Wireshark coloring hints bg/fg
- range and frames accept ?filter=<display filter>, applied before
  counting and slicing; invalid expressions are 400 carrying sharkd's
  original text; filters travel as single argv-style elements, capped
  at 2000 chars; filtered frames keep their original pcap frame numbers
- frame detail returns sharkd's protocol tree with keys renamed into
  the REST contract (element/label/name/filter_expr/pos+size/expert/
  generated/children): a census-verified closed key set, values
  untouched, unknown keys passed through verbatim, Wireshark-internal
  hf ids dropped. filter_expr gives the UI click-to-filter; pos/size
  drives hex highlighting (hex still read straight from the pcap)
- one resident 'sharkd -' session per source pcap: lazy spawn, /tmp
  scratch copy + scratch HOME (hardened profiles), per-request
  (mtime,size) validation with respawn, LRU bound, per-session lock,
  per-RPC timeout, bounded close

Timeline backbone (gate, record-header scan, merge ordering, canonical
ts strings, hex reads) stays plain Python — identity and ordering never
depend on the engine.
2026-09-08 00:04:04 +08:00
YueGuobin c646d6df5a feat: add tag-keyed aggregate replay over paused markers' pcaps
Markers on different links sharing a tag form one distributed capture
session. Once every marker under the tag is paused (409 otherwise), three
read-only endpoints replay it:

- GET .../markers/tags/{tag}/replay/range merges the per-marker pcaps by
  scanning 16-byte record headers only (no tshark) into a timestamp-
  ordered frame list (per-second buckets above a 5000-frame cap)
- GET .../replay/frames?ts=&window_ms= returns frames in [T, T+window];
  an empty window is a normal empty array
- GET .../replay/frame/detail lazily decodes one frame the user opened:
  raw bytes for the hex view read straight from the pcap, protocol tree
  from 'tshark -T pdml' mapped isomorphically to JSON (every attribute
  survives, values stay strings). tshark reads a /tmp scratch copy with a
  scratch HOME — hardened profiles deny it the project directory.

Sort key is (ts, source file, frame number): ts is not unique across a
merge. The ts parameter round-trips as the exact string from the frame
list. Round-trip tests pin the PDML→JSON fidelity (element count and
attribute coverage).
2026-09-05 21:49:24 +08:00
Jeremy Grossmann d1b4de6b8f Merge pull request #2868 from yueguobin/feat/template-delete-usage-guard
feat: forbid deleting templates and images still used by projects
2026-08-31 19:03:28 +02:00
YueGuobin eed981fee3 fix: tear down controller console WebSocket forwarding cleanly on client disconnect
Closing a web console while the node kept streaming output crashed the
ws_console/vnc_console handlers with an uncaught WebSocketDisconnect from
the compute-to-client send path (only the opposite direction was guarded),
producing a full ASGI traceback on every console close.

Restructure both endpoints as symmetric forwarding tasks managed with
asyncio.wait(FIRST_COMPLETED): exceptions from either direction are
collected as task exceptions, the peer task is cancelled, the compute
WebSocket is closed, and the client is notified. The receive loops now
close and log on every exit path instead of only in the exception branch.

Tests patch the in-memory ASGI transport to deliver a conformant
websocket.disconnect on client close (it sends a non-conformant
websocket.close that starlette receive() rejects).
2026-08-31 22:43:41 +08:00
YueGuobin 13548deae8 feat: forbid deleting templates and images still used by projects
Deleting a template with prune_images, deleting an image, or pruning
orphan images only checked template references — a project node still
pointing at the image (e.g. via hda_disk_image_backing_file) was left
with a dangling reference and the project could no longer be opened.

- Add controller helpers scanning every known project (opened projects
  via in-memory nodes, closed projects via their .gns3 file) for
  template and image usage; unreadable topologies are skipped
- Guard DELETE /templates/{id}, DELETE /templates/{id}?prune_images,
  DELETE /images/{path} and /images/prune with a 409 listing the
  project names
- Run all template-delete checks before any mutation so a refused
  deletion cannot leave the template gone while its images survive
2026-08-31 22:30:42 +08:00
YueGuobin d1edfbe5e8 fix: replace Bearer JWTs with path-bound access tickets in MCP download tools
link_capture_download and get_symbol embedded a 10-min JWT in the
Authorization header of the curl command they return; LLM clients
retyping that command corrupted the long token — the same failure
class as the console WebSocket URLs fixed in the previous commit.

Generalize the ticket store (console_tickets.py -> access_tickets.py,
ConsoleTicketService -> AccessTicketService): a ticket now binds to
either a node's console endpoints (WebSocket, matched against route
path params) or one exact REST resource path (capture file, symbol
image). The two binding modes are isolated — a node-bound ticket
cannot authenticate a REST resource and vice versa.

get_user_from_token redeems path-bound tickets through the existing
token parameter / Bearer header, matched exactly against
request.url.path, then reuses the shared user lookup and token_version
revocation checks. Download URLs embed ?token=<ticket> and the curl
commands no longer carry a Bearer header.
2026-08-29 01:10:19 +08:00
YueGuobin 6d6b5351fa fix: issue short-lived console tickets instead of JWTs in node_console MCP tool
LLM clients transcribing the console WebSocket URL into shell commands
reliably corrupted the ~200-char JWT embedded in it (dropped header
segment -> "MissingAlgorithmError: Missing 'alg' value in header" on
every connection attempt). The node_console tool now mints a short
random ticket ("gns3t_" + 16 urlsafe chars, 10 min TTL, multi-use)
stored server-side and bound to the node's console endpoints:

- new ConsoleTicketService (gns3server/services/console_tickets.py),
  in-memory store with lazy expiry sweeps
- get_current_active_user_from_websocket redeems tickets through the
  existing "token" query parameter, gated on websocket.path_params so a
  ticket only authenticates the console/ws and console/vnc routes of
  the node it was minted for; the JWT path is unchanged
- redemption reuses the existing user lookup, token_version revocation
  and is_active checks, so logging out invalidates outstanding tickets
- vnc_url no longer embeds the full session JWT
- the tool docstring now tells clients to run the returned command
  verbatim instead of reconstructing the URL
2026-08-29 01:00:12 +08:00
YueGuobin 2324dcd744 fix: handle client disconnect in console WebSocket forwarding
The compute-to-client forwarding loops in ws_console and vnc_console had
no WebSocketDisconnect handling: when a client (WebUI, or an MCP-driven
websocat session killed by timeout) disconnected while the compute was
still streaming console output, the next send raised WebSocketDisconnect
that leaked all the way up to uvicorn as an ERROR-level ASGI traceback.

Catch it and log at info level, symmetric with the receive-side handlers.
2026-08-28 23:35:33 +08:00
YueGuobin 8a8314ab29 fix: dedupe and report automatic template creation from images
install_appliances_from_image relied on the name+version pair check in
TemplatesService, so the same appliance reached through a second image
(the CSR1000v case) created a second template sharing the name. The auto
path now skips when any template with the same name exists, whatever the
version, and returns a manifest of created and skipped candidates;
POST /images/install replies 200 with that manifest instead of an empty
204, and the image_install MCP tool surfaces it.
2026-08-26 00:04:57 +08:00