Skip to content

Add visualised node graph feature#8

Open
Copilot wants to merge 2 commits into
mainfrom
copilot/add-visualised-node-graph
Open

Add visualised node graph feature#8
Copilot wants to merge 2 commits into
mainfrom
copilot/add-visualised-node-graph

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 22, 2026

Adds an experimental Visualise page that renders the Headscale ACL policy and node/user data as an interactive graph.

Features

  • Graph construction – builds a typed graph from users, nodes, groups, tag owners, hosts, and ACL policy entries, resolving src/dst endpoints to graph node IDs.
  • MDI icons – each entity kind is represented by its corresponding MDI icon (mdi:account, mdi:account-group, mdi:tag, mdi:laptop, mdi:server, mdi:asterisk) inside a coloured background circle, matching the application's existing design language.
  • Force-directed Y layout – a Fruchterman-Reingold-style spring simulation refines Y positions after initial column placement. Column X order (user → group → tag → node → host → wildcard) is preserved; connected nodes are attracted to the same vertical level, substantially reducing edge crossings.
  • Per-kind node cap – a trimGraph helper limits each entity kind to 10 nodes. When the graph exceeds the limit a "Note" banner prompts the user to narrow their search or apply filters.
  • Filtering – sidebar controls to show/hide entity kinds and relation types, plus a free-text search. Entity and relation counts are shown live.
  • Selection / detail panel – clicking a node highlights its direct neighbours and lists them as clickable chips in a detail panel below the graph.
  • Edge presentation – directed arrows terminate at the node circumference (not the centre); ACL-accept edges are thicker and green; tag-node edges are dashed.
  • Navigation – linked from the main navigation as "Visualise" with a mdi:graph-outline icon.
  • E2E and unit tests – Playwright smoke tests for the page, and 27 unit tests covering graph construction, filtering, layout, trimGraph, and endpoint resolution.

@CJFWeatherhead
Copy link
Copy Markdown
Member

@copilot resolve the merge conflicts in this pull request

Copy link
Copy Markdown
Author

Copilot AI commented Apr 23, 2026

@copilot resolve the merge conflicts in this pull request

Resolved and pushed in commit 0383318.

No UI files were changed as part of this merge-conflict fix, so there is no UI screenshot to attach.

Copilot AI requested a review from CJFWeatherhead April 23, 2026 11:12
@EdGeraghty EdGeraghty force-pushed the copilot/add-visualised-node-graph branch from 0383318 to edf93a8 Compare April 23, 2026 11:54
@CJFWeatherhead CJFWeatherhead marked this pull request as ready for review April 23, 2026 15:17
@CJFWeatherhead
Copy link
Copy Markdown
Member

@copilot While this is a great initial implementation, its usability is poor, many of the lines intersect and it make is difficult to actually make any inferences from the data. I think that the nodes should be more fluid, they should avoid intersecting with on another, its okay to put limits to achieve the (e.g only 10 nodes at a time can be visualised). I think at the presentation level everything is a little small, I think it might also be worth using mdi icons rather than dots, to align with the rest of the application's design language.

…, larger nodes

Agent-Logs-Url: https://github.com/privacyint/headscale-admin/sessions/d5febefb-cfb0-4150-8d59-1d08dbcd4770

Co-authored-by: CJFWeatherhead <6923098+CJFWeatherhead@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented May 1, 2026

@copilot While this is a great initial implementation, its usability is poor, many of the lines intersect and it make is difficult to actually make any inferences from the data. I th...

Addressed in commit dd2124d. Here's a summary of what changed:

MDI icons — circles replaced with coloured background discs containing proper MDI icon shapes (mdi:account, mdi:account-group, mdi:tag, mdi:laptop, mdi:server, mdi:asterisk), matching the rest of the application's design language. The legend and detail panel also use the icons.

Reduced intersections (Y-force simulation)layoutGraph now runs a 60-iteration Fruchterman-Reingold-style spring simulation in the Y direction after the initial column placement. Connected nodes are attracted toward the same vertical level, so edges in the same column pair become roughly parallel rather than crossing.

Node size / presentation — node radius increased from 10 → 20 px (selected: 26 px), default row spacing from 56 → 90 px, padding from 40 → 60 px, font size 12 → 13 px, arrow markers widened. Edge lines now terminate at the node circumference rather than the centre.

Per-kind cap — a trimGraph helper limits each entity kind to 10 nodes. When the graph exceeds that limit a dismissable "Note" banner prompts the user to narrow their search or filter.

Here is the graph rendering with the full mock dataset:

Visualise graph with MDI icons and improved layout

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5384 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-8p6exb --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9826532687574550594,3524122290550472411,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
    • Triggering command: /usr/bin/chromium /usr/bin/chromium --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl it (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5510 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-0lZAJJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,3017220171682055419,8999240499167683648,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
  • android.clients.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5384 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-8p6exb --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9826532687574550594,3524122290550472411,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
    • Triggering command: /usr/bin/chromium /usr/bin/chromium --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl it (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5510 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-0lZAJJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,3017220171682055419,8999240499167683648,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
  • clients2.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5384 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-8p6exb --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9826532687574550594,3524122290550472411,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
    • Triggering command: /usr/bin/chromium /usr/bin/chromium --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl it (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5510 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-0lZAJJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,3017220171682055419,8999240499167683648,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
  • content-autofill.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5384 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-8p6exb --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9826532687574550594,3524122290550472411,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
    • Triggering command: /usr/bin/chromium /usr/bin/chromium --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl it (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5510 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-0lZAJJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,3017220171682055419,8999240499167683648,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5384 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-8p6exb --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9826532687574550594,3524122290550472411,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)
    • Triggering command: /usr/bin/chromium /usr/bin/chromium --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl it (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --disable-dev-shm-usage --use-angle=swiftshader-webgl --mute-audio --crashpad-handler-pid=5510 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/playwright_chromiumdev_profile-0lZAJJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,3017220171682055419,8999240499167683648,262144 --enable-features=CDPScreenshotNewSurface --disable-features=AutoDeElevate,AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,OptimizationHints,PaintHolding,Rend (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Developing visualised node graph feature Add visualised node graph feature May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants