Compare commits

...
528 Commits
Author SHA1 Message Date
Eduard GertandClaude Opus 5 84c491a722 Bind the assistant-access grant to the window that asked for it
The page at /peer/assistant-access read its peer, WireGuard key and rules
from the URL and, once the forced sign-in came back, granted temporary SSH
access without asking anything further. The sign-in proves a person is
present; it does not prove they meant to grant that key access to that
machine. A link mailed to an admin was therefore enough: click, sign in to
a prompt you recognise, and a key you have never seen lands on the network.

Two checks close it, both before the sign-in so a link never gets to borrow
an identity provider's credibility:

The opener now mints a nonce into sessionStorage before calling
window.open, and the page refuses to grant anything without it. A window
opened from the panel inherits a copy of that storage; a window reached by
following a link does not. It is spent before the POST, so a reload or a
Back inside the window cannot grant twice, and dropped on every path that
settles the request.

The rules are checked against the set this flow may ask for rather than
passed through as given — tcp/3389 would be RDP, not SSH. A test drives the
executor across its version thresholds and asserts every rule it produces
is one the page accepts, since the two halves live in different files and
drift either way is silent.

Also drops the "eve" codename from assistant comments and test names, and
removes an eslint ignore for an assistant/ directory and CI workflow that
no longer exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:42:54 +02:00
Eduard GertandClaude Opus 5 53f166b4ef Run a command on a peer from the assistant
The browser half of `ssh_run_command`. It resolves the peer against the real
peer list, then blocks: one prompt asks for the command and, the first time for
a peer, the sign-in that authorizes it. Both questions at the same moment,
which eve's own `approval` cannot do — it parks a call before it runs, so it
could only ever ask about the command and leave the sign-in as a surprise
afterwards.

The executor now reports what it is waiting on, because the trail row's label
comes from the tool name and reads "Running a command" through the whole
stretch in which nothing is running and the assistant is waiting on a person.

The authorization window closes itself once the grant lands. It kept a Close
button for a window whose job was done; the button now appears only if the
browser refuses to close it.

Also widens the wasm ignore rule, which matched an exact path and so let a
47MB `.bak-pre-exec` sibling be staged for commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:07:59 +02:00
Eduard GertandClaude Opus 5 ba96ebd965 Set a policy's protocol and ports when the draft creates it
The canvas half of the same change: a new policy carries its protocol and ports
from birth, so it is never briefly drawn as a rule open to everything while a
second write narrows it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:07:47 +02:00
Eduard GertandClaude Opus 5 9e7d60cfff Clip the nav rail instead of letting it grow a scrollbar
Radix sizes a ScrollArea's content box with `display: table`, so an item wider
than the rail — a long label with a badge, which `whitespace-nowrap` forbids
wrapping — widened the box and the rail grew a horizontal scrollbar. The rail's
width is fixed, so that overflow is something to clip, never something to
scroll.

Also adds the shared table column breakpoints, so columns drop in a stated
order rather than each table deciding for itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 15:07:47 +02:00
Eduard Gert ccaf509fc7 Fix nav 2026-09-17 09:59:07 +02:00
Eduard GertandClaude Opus 5 ea508de448 Render framework pauses in the assistant chat, and settle the activity rows
useAssistant has exposed pendingInput and respondToInput for a while and
nothing rendered them. eve parks a session on an input request — a tool
approval, a runtime limit — and answers nothing until one comes back,
including every message sent after it. Unrendered, that is a chat that stops
replying with no error and no way to tell it apart from a hang.

AssistantApprovalCard takes the composer's place while a pause is open, rather
than floating above it: the box you would have typed into is exactly the thing
that should not be there. It titles itself from TOOL_MANIFEST so the prompt
reads in the same words as the rest of the thread, orders the choices deny-left
and allow-right so the key bound to Enter and the button a pointer lands on are
the same one, and latches after the first answer because the session is parked
until it lands.

The activity rows had three faults that only showed up while watching a tool
actually run:

- A row kept its spinner after the call failed, because "running" came from
  the runtime's last word rather than from having a result in hand. A result
  now outranks the status, and a finished call always ends with a mark: a tick
  when it worked, an info circle when it did not.
- The working indicator is one status line shared by the thread but rendered
  per message, so a turn that left an earlier message unfinished stacked
  "Thinking…" once per message. Only the last message may claim it.
- The copy button reserved a row of height under messages that were all tool
  calls and had nothing to copy, which is what opened the gap between the last
  step and the working line. It now appears only where there is an answer, and
  tool-only messages are spaced as the work they are rather than as replies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:30:07 +02:00
Eduard Gert bee1d3aafb Merge branch 'main' into feat/ai-assistant
# Conflicts:
#	package-lock.json
#	src/layouts/DashboardLayout.tsx
#	src/layouts/Navigation.tsx
2026-09-15 13:35:43 +02:00
Maycon SantosandClaude Fable 5.1 da81cb7f50 Pick a private-capable proxy cluster when bootstrapping the agent network (#780)
* Pick a private-capable proxy cluster when bootstrapping the agent network

The connect-provider wizard bootstraps the account's agent network
endpoint from a proxy cluster it auto-picks off /reverse-proxies/domains,
taking the first validated free domain. That list includes clusters that
cannot host the endpoint: the endpoint is a private service, reachable
only from connected peers and authorised by their tunnel identity, which
needs a cluster with at least one connected embedded proxy — the same
supports_private flag the Reverse Proxy modal gates NetBird-Only Access
on. Management refuses a bootstrap onto a cluster reporting it false, so
the wizard could propose a pick the API rejects, and the endpoint it
assigns is immutable.

Filter the candidates on supports_private before auto-picking, and give
the "nothing to pick" callout a second message for the case where
clusters exist but none of them has an embedded proxy — a different
problem from having no proxy at all, and a different fix.

Only an explicit false disqualifies a cluster: a management build that
predates the flag reports nothing at all, and dropping every cluster
there would block setup on a backend that would have accepted it.

Callout takes an optional data-testid so a spec can assert on the state
it announces rather than on its copy.

* Derive the bootstrap cluster instead of holding it in state

Nothing picks the bootstrap cluster: there is no selector, so the state
could only ever mirror the first usable entry of the domains list, with
an effect writing it back on arrival and a render pass to show it. Derive
it from that list instead, which drops the state, the effect and the two
resets that kept them in sync.

* Declare the e2e proxy private so a bootstrap cluster exists

The connect-provider wizard now offers only clusters whose proxies report
the private capability, because the agent network endpoint is a private
service and management refuses to bootstrap it anywhere else. The e2e
environment's reverse-proxy ran with NB_PROXY_PRIVATE at its default of
false, so with that filter in place no cluster qualified: the wizard
showed the no-cluster callout, Continue stayed disabled, and every spec
that walks through the wizard failed on the Models tab it could no longer
reach — agentgateway, Kimi and the refused-save spec.

Declare the capability on the primary proxy. The no-ports proxy stays
centralised, so the pick also has to skip past a cluster that does not
qualify, which is the case the wizard filter exists for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6

* Send the no-cluster callout to the agent network Clusters section

The callout told users to connect a proxy under Reverse Proxy, the generic
services page. Agent Network has its own Clusters tab under Configuration,
which is where a user setting up the agent network expects to manage the
clusters it runs on, so both messages link there instead. The tab and the
callout gate on the same permission (services read), so every role that
sees the callout can follow the link.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6

* Say private capabilities, not embedded proxy, in the no-cluster callout

The requirement is a cluster with private capabilities, which is what the
supports_private flag reports. Tying the message to an embedded proxy named
one way of getting there as if it were the requirement itself, and pointed
users at the wrong fix. The callout now states the requirement and where to
connect such a cluster.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sa3DsBDP3VciAi4PPG17L6

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 22:19:37 +02:00
Jack Carter f4e80d37c4 feat: restore the Active toggle and keep it visible on disabled rows (#803)
Disabled routing peers, network resources, policies and legacy routes were
only distinguishable by a faded row, and the fade also dimmed the controls
needed to re-enable them. Bring back the Active toggle column that was
hidden in #654, and add a cellClassName hook to DataTable with a shared
fadeDisabledRowCells helper that fades every cell of a disabled row except
the toggle and actions cells.
2026-09-14 15:43:50 +02:00
Pascal Fischer de045582e5 mark is cloud false unless explicitly overridden (#799) 2026-09-12 09:07:45 +02:00
Eduard GertandClaude Opus 5 6a85748b91 chore: update Next.js to 16.3.4 and refresh dependencies (#797)
Bump next from 16.3.0 to 16.3.4 and refresh the transitive lockfile
entries that came with it (@next/* swc binaries, sharp 0.35.4,
rolldown, vitest, playwright).

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 14:47:14 +02:00
Nicolas Frati a9a91905fa Add rootless dashboard container image (#791) 2026-09-09 23:00:19 +02:00
Eduard Gert 3459a4db28 chore: remove dead code and unused dependencies (#787)
Remove the unreachable FirewallGPT module and the abandoned traffic-event
detail prototypes, along with the dependencies they were the sole consumers of.

FirewallGPT was disabled in 9d8d9ee4 by removing its trigger button, leaving
the modal mounted but impossible to open. TrafficEventDetails was never
imported anywhere, and it was the only consumer of TrafficEventChart.

Dependency changes:

- Drop @faker-js/faker, whose only usage was mock chart data in the removed
  TrafficEventChart. This also resolves GHSA-qxc2-j82w-r537 without needing
  the 9 -> 10 major upgrade.
- Drop react-confetti-explosion, only used by the removed
  FirewallGPTSuccessModal.
- Bump @types/node to 20.19.43 so it satisfies the peer range of the vite
  version pulled in by vitest, silencing an ERESOLVE warning on install.
  Stays on the 20.x line to match the Node 20 used in CI.
- Add an allowScripts deny policy for the four packages with install scripts.
  Both @axa-fr postinstalls are bare echo statements, fsevents ships a
  prebuilt binary, and the unrs-resolver postinstall only verifies a binding
  that optionalDependencies already installs.
2026-09-09 11:38:38 +02:00
Maycon Santos cba2613e68 Add anonymization level and upload URL to the debug bundle modal (#753)
Surfaces the two bundle parameters added to the management API: an
anonymization level (Default/Strict, shown when anonymization is enabled)
and an optional upload service URL that overrides the default upload
server. The upload URL is validated as an https URL before the job can be
submitted; both fields are omitted from the request when left at their
defaults so the peer applies its own.
2026-09-03 20:05:02 +02:00
Maycon Santos fe32179dc1 Pin the group view's group, and give agentgateway a reachable upstream (#788)
Stabilize the flaky Control Center and agentgateway e2e specs (#788)

  Three separate causes behind the recent e2e failures.

  The group view ranked every group in the account by peer count to decide what
  to display, so with two workers it could land on a group another spec had just
  created. openGroupView() selects the group by name instead.

  Management now checks a provider's upstream URL when the provider is saved and
  rejects it unless the URL returns a model list, so the agentgateway spec's
  placeholder host stopped working. The test environment runs a stub that answers
  the check.

  The Backspace test blurred the group panel's search input before that input had
  taken focus, so it reclaimed focus and swallowed the key.
2026-09-03 18:38:12 +02:00
Eduard Gert 1bad86b277 Show peer-sourced policies in the Control Center peer view (#786)
The peer view matched policies only by source group membership, so a
policy whose source is the peer itself never appeared in that peer's
view. There was no group fallback either: a rule carrying a
sourceResource has its sources nulled on save, so the group check could
only ever return false for exactly these policies.

Such policies are reachable from the canvas itself, since dragging a
connection off a peer node writes a sourceResource rather than a group.
A user could create a peer-sourced policy, deploy it, drill into that
same peer and not see it.

Match rule.sourceResource against the peer id alongside the group check.
The rendering side needs no change, as select-peer-node is the peer and
its edge to the policy is drawn unconditionally. Draft mode is fixed
transitively: useDraft harvests visible policies from the live view's
nodes, and already draws a sourceResource peer as a source card.
2026-09-03 14:29:06 +02:00
Maycon Santos 3c72444e83 Keep a refused provider save on screen, and its discovered prices (#772)
A refused save closed the modal anyway, throwing away the key just typed — and
the API never returns one. Both paths now stop before closing. The failure was
also reported twice and, because notify() only reddens through its promise path,
both times as a success.

Load and submit now spin, say what they are waiting on, and stop taking clicks;
dismissal is refused mid-save, since the write lands regardless and would
otherwise close a session reopened in the meantime.

Discovery carries the record id and the form's URL together, so changing an
endpoint no longer demands the key back. A 422 from it was reported as
unreachable rather than unsupported — useApiCall rejects with { code, message }
and the hook read status, leaving the catalog fallback unreachable.

Discovered models arrived at zero: 0/0 was hardcoded for anything the catalog
lacked by exact id, which stopped being true once discovery returned real rates.
Bedrock felt all of it — its geography-prefixed ids never match by string.

A Playwright spec covers the refusal end to end.
2026-09-02 21:47:47 +02:00
Nicolas Frati 386fb12013 fix(e2e): stabilize agentgateway provider test (#782) 2026-09-02 19:09:36 +02:00
Daneyon Hansen e3cc74522c feat(agent-network): add agentgateway provider support (#774)
* feat(agent-network): add agentgateway provider UI

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* fix(agent-network): address agentgateway review feedback

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

* fix(agent-network): align with updated dashboard lint

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>

---------

Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>
2026-09-02 01:01:13 +02:00
Maycon Santosandbraginini 1537d31f0d Add Agent Network Admin and Usage Viewer roles, gate Agent Network per submodule (#764)
* Add Agent Network Admin and Usage Viewer roles, gate Agent Network per submodule

The management server now ships two delegated roles (agent_network_admin,
usage_viewer) and reports agent_network submodule permissions
(agent_network.providers, .policies, .usage, .logs, .settings, ...) in
/users/current. The dashboard still gated the whole Agent Network section
on services.read, so the new roles saw either everything or nothing.

- register the agent_network module keys so PermissionsProvider fills
  denied defaults and typed lookups work
- gate the Agent Network sidebar and pages per submodule: providers,
  policies, usage (usage or logs read), configuration (settings)
- show the section when any child is permitted instead of services.read
- add both roles to the role selector, user/invite tables and badges

* Gate Usage and Clusters tabs by their own permissions

A caller holding only agent_network.logs.read landed on the Usage tab it
cannot read, and one holding only agent_network.usage.read could open
Access Logs. Show each tab only with its matching read permission and
fall back to the first permitted tab on deep links.

The Clusters tab is a reverse-proxy surface: its table and controls run
on the services permission, so agent_network.settings alone (e.g.
agent_network_admin) no longer surfaces it.

* Fall back to a known configuration tab for unknown query values

An unknown ?tab= value selected no content pane and left the
configuration page blank; unknown or unauthorized values now fall back
to Global Limits.

* Derive the configuration tab from the URL query

The ?tab= query was mirrored into local state through an effect, which
can lag a render behind the URL and duplicates the source of truth.
Derive the active tab from the query directly; trigger clicks already
push the query themselves, and onChange keeps pushing it for Radix's
keyboard navigation, which fires without a click.

* Derive the usage tab from the URL query

Same treatment as the configuration page: the ?tab= query was mirrored
into local state through an effect, duplicating the source of truth.
Derive the active tab from the query; onTabChange keeps pushing it so
clicks, deep links and back/forward all resolve the same way.

* Group user roles into General and Agent Network tabs

  Tab the role dropdown by product surface, gate the Agent Network
  roles and tab on the Agent Network surface being available, and
  disable the role field for the owner unless the caller is an owner.

* Point agent network learn-more links at their docs pages

* Skip the domains lookup when the role can't read it

* Flatten the role selector and add a caller-scoped My Setup page

The role dropdown split General and Agent Network roles into tabs,
which reads as two products inside one field. Return to the single
flat list from main, keeping the surface gate (Agent Network roles
hidden when the surface is unavailable) and the owner protections.

Regular users had no way to see which providers and models their
policies allow or where to point their tools. Add
/agent-network/my-setup backed by the caller-scoped me/setup and
me/consumption endpoints: the endpoint with copy, per-provider
available models, the Configure Your Agent instructions flattened
inline from the connect modal, and the caller's own usage counters.
The sidebar surfaces it for any role — limited view included —
whenever the caller's setup is configured, and stays hidden when
not, which is indistinguishable from no access by design.

* Fix delegated-role API errors and reuse the usage overview for My Usage

Field testing surfaced three problems. The policy and budget modals'
group selector fetched /networks/resources unconditionally, so an
agent_network_admin without the networks permission got a 403 and an
error toast on the policies page; gate the fetch on networks read.

Restricted users got no navigation at all, so the limited view could
never reach My Setup; render the sidebar for restricted users when
their setup is configured or their role reads an agent_network surface.
Roles below account admin also cannot read the account feature flag
(accounts read), so holding an explicit agent_network grant now counts
as proof the surface exists, while callers with accounts read keep the
flag as the source of truth.

My Usage rendered raw per-window counter rows, nothing like the admin
usage view; render it with the same chart-and-table component as the
admin overview, fed from the caller-scoped me/usage/overview endpoint
that returns the identical bucket shape filtered to the current user.

* Reuse the providers-page agent config on My Setup and self-scope Usage & Logs

My Setup now presents the endpoint exactly like the providers page — the
API Base URL badge with Copy and Agent Config opening the same modal
(provider ids come from the caller's me/setup answer instead of the
admin-permissioned provider context) — and drops the My Usage tab: the
regular Usage & Logs page now serves callers without the account-wide
grants too, showing their own data (the server pins the filters to them),
with the identity and provider filters trimmed since they'd be overridden.

usage_viewer gets working filters: the feature-flag fallback moves into
useAgentNetworkMode, AIProvidersProvider gates each fetch on its
submodule read grant so partially-granted roles don't fire 403s, and
GroupsProvider serves restricted roles that hold the groups read grant.
The limited view also gains the Peers link (the add-your-device page).

* Hide provider write actions from read-only viewers

usage_viewer now reads the provider list, but the page still offered every
write flow: Connect Provider, row-click edit, and the enable/disable/delete
menu. Clicking any of them opened the wizard, which - unable to read the
settings row - warned that Agent Network isn't set up even on configured
accounts. Each action is now gated on its own operation grant: the connect
button needs providers create, row-click edit needs update, and the menu
items need update/delete respectively (no menu at all without either).

* Restore the grouped role selector tabs

Bring back the General and Agent Network tabs in the role dropdown
from 3fbeb1e, undoing the flattening: the tabbed grouping keeps the
list readable as more surfaces ship their own delegated roles. The
surface gate (Agent Network roles hidden when the surface is
unavailable) and the owner protections are unchanged.

* Offer the provider and model filters to self-scoped callers

The providers endpoint now self-scopes on the server, so a configured
plain user fetches it too and the caller's own authorized providers back
the Provider and Model filter options on the self-scoped Usage & Logs
view. The identity filters stay hidden — the server pins user and group
to the caller regardless.

* Give the agent config a single home on Connect Agent

  The endpoint card carried an Agent Config action on both the providers
  page and the caller-scoped self-service page, so the same connect
  snippets had two entry points. EndpointBadge now renders it only when
  the caller passes providerIds, which is just the self-service page; the
  providers page keeps the URL and Copy. The context-backed
  AgentConnectModal wrapper is then unused, so it goes.

  Rename that page from My Setup to Connect Agent in the sidebar,
  breadcrumb and heading: My Setup was the only first-person label in the
  navigation, and Connect Agent stays clear of the Configuration sibling.

* Simplify Bedrock config

* Inline the agent config on the Connect page

  Drop the Agent Config modal: its snippets now render inline under the
  endpoint card, so AgentConnectModal is just AgentConnectTabs and
  EndpointBadge is back to the URL and Copy. Move the route to
  /agent-network/connect and swap the hand-rolled provider list for the
  standard DataTable. Follow the backend rename of me/setup to
  agent-config.

* Show a policy's allowed models in the policies table

  A policy's model restrictions live on the guardrails attached to it, so
  the new Models column unions the enabled model allowlists: one chip for
  a single model, a count with the names on hover past that, and a
  Restrict badge opening the guardrails tab when nothing narrows the
  policy. Clicking the column lands on that tab too.

  Chip the models on the Connect Agent table instead of comma-joining
  them, and capitalize the model counts across all three tables to match
  the "2 Groups" chips on Peers.

* Point the Kimi e2e config assertions at the Connect Agent page

The Kimi-gated config surfaces moved: the Agent Config modal is gone and
the snippets render inline on /agent-network/connect, so the spec stopped
finding the button it used to click on the providers page. Navigate to the
Connect page instead.

That page's answer is caller-scoped — it only offers providers the
caller's own policies authorize — so the spec now grants the owner the
freshly connected provider through a dedicated group + policy, torn down
again (group detached from the owner first) before and after the run. The
backing GET /agent-network/agent-config ships with newer management
builds; on builds that predate it the suite skips, same as the existing
catalog and settings-bootstrap probes.

* Hand the agent config to every account member

  The agent-config answer withheld the endpoint from callers no policy
  covers, so "account not set up" and "you have no access" were the same
  shape. The dashboard shows every user the same connection config, so
  Configured now tracks the account: once it has an endpoint every member
  gets it, with an empty providers list for those no policy covers. That
  list stays caller-scoped, and the endpoint authorizes nothing on its own
  since the gateway still refuses every request no policy permits.

  Rename the files and symbols the endpoint outgrew when it became
  /agent-network/agent-config: me_handler.go, setup.go and types/setup.go
  become agent_config.go, GetSetupForUser becomes GetAgentConfigForUser,
  and the AgentNetworkMeSetup schema becomes AgentNetworkAgentConfig.
  Regenerating types.gen.go from the untouched spec first reproduced it
  byte for byte, so the generated diff is only these renames.

* Let the surface switch decide the Agent Network section

  Connect Agent and Usage & Logs were visible on the caller's own setup
  alone, so a deployment with Agent Network off still showed them, and the
  route tree then 404d the links the nav had just rendered. The switch now
  decides first.

  The caller's agent config still stands in for the switch, but only for
  callers who cannot read the config themselves - otherwise an account
  holding an endpoint kept the surface on for its owner regardless.

* Collapse the two User types the merge left behind

  Both sides of the merge declared a User type, in different regions of the
  helper, so the merge took both without ever reporting a conflict and
  Playwright refused to parse the file. One type now carries the union of
  the fields the /users answer returns.

* Fix SNAPSHOT version display

* Fix e2e tests

* Fix e2e tests

* Fix version display

* Fix e2e

* Fix e2e

* Fix e2e

---------

Co-authored-by: braginini <bangvalo@gmail.com>
2026-09-01 14:35:18 +02:00
Eduard GertandClaude Opus 5 cb48c7dbd4 Extract assistant into the @netbird/assistant-react SDK
The assistant backend (assistant/) and the dashboard-side agent runtime,
tool definitions, and canvas agent store all moved into the assistant
repo and now ship as a packed SDK consumed from ./vendor. The dashboard
keeps only what is genuinely app-specific.

- Drop assistant/ and its CI workflow, plus the local runtime, tool
  registry, redaction/PII helpers, and canvasAgentStore that the SDK
  now owns.
- Keep openPageExecutor app-side: only the dashboard knows its own
  routes. executorParity.test.ts asserts every client tool in the SDK
  manifest has an executor, and that we register none the manifest
  doesn't dispatch to the client.
- Add usePageContextString so page context is read at send time through
  a ref, since the getter is captured once per conversation.
- Swap the ai-sdk/zustand deps for the vendored SDK, transpile it in
  next.config.js, and pin the assistant-ui paths in tsconfig so the app
  and the SDK resolve one copy.
- gitignore vendor/: the tarballs are packed by scripts, not committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 09:55:02 +02:00
Brandon Hopkins 4f905a4368 Add bulk deletion for Access Control groups (#752)
* Add multi-select bulk deletion for Access Control groups

* Add indeterminate group selection and permission-safe column visibility

* Background rule for checkbox component

* Better show when disabled
2026-08-27 17:32:25 +02:00
Eduard GertandClaude Fable 5 5d15f8c109 Merge origin/main into feat/ai-assistant
Every conflicted file is byte-identical between main and the already
merged PR #744 branch, so the previous merge's resolutions stand as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:22:26 +02:00
Eduard GertandClaude Fable 5 2130a0a334 Merge control-center draft-mode branch (unsquashed PR #744) into feat/ai-assistant
Main squashed this branch as #744, which made a direct merge of main
produce ~150 add/add conflicts with no usable base. Merging the real
PR branch first gives git the shared history; main follows on top.

Both sides evolved the control center in parallel after this branch
last synced. Resolution policy: the PR's later fixes (deploy integrity,
group-deletion cascade, install-peer lifecycle, artifact teardown,
resourceEnabled split) become the new base; this branch's features
(assistant bridge, routers on placeholder peers, pendingInstall rows,
group-panel canvas offers) are re-applied on top of them. Where the PR
had blocked editing placeholder-bound routers as a stopgap, the full
placeholder support wins, and its unit test now pins the new behavior.
Fixes ported across the seam: the resource-enabled toggle in
useDraftNodeActions adopts the live-baseline semantics, useAutoArrange
writes through the ReactFlow instance instead of useCanvasState, and
useDraftGroupActions regains an unconfirmed deleteGroup for the
assistant path over the new cascade-aware deleteGroups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:21:46 +02:00
Eduard Gert 5cf8995c65 Detect Entra connection from auth service SSO connections (#776) 2026-08-27 11:51:27 +02:00
Eduard Gert ca16b4868b Add control center draft mode (#744) 2026-08-27 10:08:22 +02:00
Shaban Mohammedsaani Hassan d3ff3bafca fix: use --restart=unless-stopped instead of --rm in Docker install command (#760)
The generated Docker command used --rm, which removes the container on
stop. Combined with -d, the NetBird client would not survive a host
reboot despite the persistent netbird-client volume. Replace with
--restart=unless-stopped so the client reconnects automatically.
2026-08-27 09:51:35 +02:00
Eduard Gert 712d0b8fdb Merge remote-tracking branch 'origin/main' into feature/control-center-draft-mode 2026-08-26 16:50:14 +02:00
Eduard GertandClaude Fable 5 e694f1e845 Land on the peer view after deploying a draft
The peer view is the control center's entry default, but a deploy
exited back to whatever view the draft was entered from. exitAfterDeploy
now routes through onViewChange(PEERS), which also resets the layout,
clears selections and drops the ?tab= param. Cancel/discard still
returns to the previous view since nothing landed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 16:50:04 +02:00
Eduard GertandClaude Fable 5 e494ce1b38 Drop the card chrome behind canvas empty states
The control center's peers empty state kept the Card's gray background
and border while the network empty states stripped them, because
showBackground only toggled the skeleton filler inside the card. Every
showBackground={false} caller also wants a transparent card, so fold
the two knobs: the flag now drops the card chrome too, and the separate
cardClassName prop is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 16:41:22 +02:00
4ca8cc09a4 Fix typo in PeerExpirationToggle component (#665)
* Fix typo in PeerExpirationToggle component

* Fix the same typo in PeerActionCell

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 14:52:28 +02:00
Nicolas Frati 9e6d512ed1 ci: skip Docker publishing for community PRs (#775) 2026-08-26 14:40:03 +02:00
Viktor Liu 7b60d87652 Add a workflow bumping the WASM client on netbird releases (#770)
* Add a workflow bumping the WASM client on netbird releases

* Cover the pinned WASM client with an instantiation test

* Connect the pinned WASM client to management in the e2e suite

* Validate the dispatched tag before it reaches the shell and satisfy lint

* Sort the spec imports

* Clean up the registered peer even when the connect test fails

* Drive the dashboard's own remote-access flow instead of the client directly

* Mint the flow test's target peer by exposing a service

* Limit the wasm check to what the test environment can prove

* Give the wasm download and compile a realistic budget

* Hold the wasm client to the dashboard's initialization deadline
2026-08-26 14:18:55 +02:00
Eduard GertandClaude Fable 5 368f6d4322 Fold the e2e run back into a single unsharded job
The six-way shard split traded one 15-minute job for six ~7-minute
environments plus a week of chasing env-setup races; not worth the
compute and the moving parts. What stays are the fixes that help a
single job too: the build overlapping env setup (gated on the rendered
.test-config.json, at lowest CPU priority), proxies skipping their
GeoLite2 download, management's geolocation databases cached across
runs, and the 480s registration budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 17:33:53 +02:00
Eduard GertandClaude Fable 5 4a937295c8 Cache management's geolocation databases across e2e runs
Management downloads GeoLite2 data on first boot (it cannot be disabled:
/locations/countries feeds the country dropdown the proxy specs use),
and a slow pkgs.netbird.io stretched env setup from 4 to 8+ minutes.

The workflow restores geo-cache/ before env setup; renderDockerCompose
bind-mounts the cached databases into management's data dir, where
getDatabaseFilename uses existing files without any network call. On a
miss, exportGeoDatabases copies the files out of the volume once
management's API serves, and the save step uploads them right after env
setup so a later test failure doesn't lose the cache. The key is static
(geolite-dbs-v1); bump it to refresh the data.

Also fixes a missing trailing newline in e2e/environment/.gitignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 17:00:17 +02:00
Eduard GertandClaude Fable 5 b27d059165 Disable proxy geolocation downloads in the e2e environment
Both reverse proxies download a GeoLite2 database at startup with a
2-minute client timeout, and a slow pkgs.netbird.io stalled proxy
registration past the wait budget — the source of the remaining shard
flakes. No spec exercises country-based enforcement, so the rendered
compose runs the proxies with NB_PROXY_DISABLE_GEOLOCATION=true.

Management keeps its geolocation download: /locations/countries feeds
the country dropdown the reverse-proxy specs select from, and disabling
it there returns a 412. The 480s registration budget stays to absorb
that first-boot download on a slow runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 16:31:51 +02:00
Eduard GertandClaude Fable 5 236b645740 Stop the overlapped build from starving the test environment
Two of six shard groups died in env setup: management's container was up
but refused connections for the whole 120s proxy-registration window.
The concurrently running Turbopack build saturates all four runner cores
exactly while management starts, so the deadline became a coin flip
(5/5 green on one run, 4/6 on the next).

The build now runs at nice -n 19 so the containers always win the
scheduler, and the proxy-registration budget doubles to 240s so a slow
runner degrades into a later start instead of a dead job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 16:10:42 +02:00
Eduard GertandClaude Fable 5 16b62a943f Run the draft matrix spec in its own e2e shard group
control-center-draft was the long pole at 10:18 while the other four
groups finished in 5-7 minutes and waited. The matrix spec dominates the
group and must stay in a quiet-graph environment, so it gets a sixth
group of its own instead of a neighbor: idle-finished groups just wait
on the slowest, so one more docker environment per push is cheap in
wall-clock terms. Expected ceiling drops to ~9 minutes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:59:41 +02:00
Eduard GertandClaude Fable 5 b984f3c21c Wait for the rendered test config before building in the e2e jobs
The build/env overlap raced next build against create-test-env.sh: the
@/config/test alias resolves to .test-config.json, which the script
renders only after its Zitadel bootstrap, so every shard group failed on
Module not found. The build now waits until the file exists and parses
as valid JSON (the render is not atomic) before starting, which still
overlaps it with the setup's back half — image pulls and proxy cluster
waits. If the setup dies before rendering, the step reports that instead
of a misleading build error; if the build fails, the background setup is
killed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:41:55 +02:00
Eduard GertandClaude Fable 5 52ce5a8fc2 Split the e2e run into five conflict-isolated shard groups
One 15-minute job becomes five parallel jobs, each with its own docker
environment, grouped by what the specs fight over rather than by count:

- control-center-draft: the specs asserting absolute canvas counts plus the
  draft-only ones; nothing here seeds live policies or registers peers.
- control-center-live: the live-object seeders and both docker-peer specs;
  only this group pulls netbird:latest.
- platform: networks, routes, access control, setup keys and the mocked
  specs — prefix-scoped and mutually safe.
- agent-proxy-dns: proxy services, DNS, agent-network, edition gating.
  crowdsec is NOT here: it creates a TCP service on listen port 3306, the
  same fixed port reverse-proxy-services-tcp starts on.
- settings-team: every account-global mutator. settings-authentication
  leaves peer approval flipped, so it can no longer poison the docker peer
  registrations two environments away.

Groups live in e2e/test-groups.json; a workflow step fails the run when a
spec is assigned to zero or two groups or the JSON names a deleted file,
so the explicit split cannot silently drop a spec from CI. login.spec.ts
is passed to every group as the auth dependency.

The Next build now runs concurrently with the docker environment setup
(CPU-bound vs network-bound), hiding ~2 minutes of the fixed cost. Files
are ordered slowest-first within each group so the workers pick up the
long poles early. Expected wall clock: 8-9.5 minutes for the heaviest
groups; the remaining floor is the environment setup itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:33:42 +02:00
Eduard GertandClaude Fable 5 c8e289dec8 Pin draft mode with a unit suite and fix what it caught
Adds 31 unit test files over the control-center draft machinery — changeset
tracking, group-deletion cascades, deploy and retry, placeholder artifacts,
leave guards, menus and views — and lands the fixes the suite surfaced:

- The navigation guard patched window.next.router, which the App Router never
  defines, so it silently guarded nothing. Guards are consulted at the call
  site via useGuardedRouter, adopted by the sidebar, header, user dropdown and
  the billing nav cards, whose <button> triggers the anchor interceptor never
  saw.
- Group deletions tag the policy writes they force, keep those tags across
  repeated deletions and hand edits (mergeGroupDeletions), and restore the
  stripped groups when the deletion is discarded. Draft policies are stripped
  too; resources and routers naming a doomed group block the deploy instead,
  since their writes land before the group DELETE.
- The deploy refuses permission-gated changes before the first request lands,
  resolves group refs id-first so a name can't capture another group's id,
  merges retry PUTs onto fresh reads instead of erasing other admins' edits,
  and pins a retry to the network its create actually ran under.
- Removing a placeholder revokes its setup key immediately; the artifact
  registry tracks key/group generations across undo, and the Install modal
  reconciles dead keys against the API on open instead of waiting forever.
- Node removal goes through one gate everywhere: bulk toolbar actions,
  keyboard Delete and the node menu share the same predicates
  (canRemoveNode, useCanDeleteGroup), and group removals run as one batch per
  gesture so every strip comes from a single canvas snapshot.
- View fixes: enabled policies win dimming across networks, a shared
  destination group clones instead of collapsing into the source column, a
  frame's dim flag no longer masks a resource's own enabled state, and large
  membership diffs skip the quadratic LCS.

Also trims the newly added comments back to the 7268557f standard: 107
deletions or compressions, -126 comment lines, no code changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 14:46:35 +02:00
Maycon Santos d72f417ff3 Keep the prices discovery returns for a loaded model (#773)
Loading a provider's models merged anything the catalog did not already carry
by exact id, and hardcoded both rates to zero on the way in. The comment gave
the reason: the discovery response carried no prices. That stopped being true
when the endpoint began returning the same rates the proxy bills with.

Bedrock feels all of it. Its listing returns geography-prefixed ids —
eu.anthropic.claude-opus-4-7 — while the catalog holds the bare
anthropic.claude-opus-4-7, so no Bedrock model ever matches by string and every
one of them takes this branch. Selecting one filled its row with $0, and the
form then warned that the model had no cost set, while the API had reported a
rate for it. Usage against it records nothing.

Exact-id matching stays. Collapsing a geography-prefixed id onto its catalog
entry would hand back the bare form, and only the prefixed one is invocable at
AWS.
2026-08-24 10:54:42 +02:00
Eduard GertandClaude Opus 5 0d39aa4dcf chore(assistant): document the deliberate render-time ref writes
Both ref assignments in useAssistantRuntime are intentional and the lint rule
cannot see why: contextRef has to be written during render or the transport
carries a commit-stale pageContext, and chatRef breaks the cycle between useChat
and the tool runner it calls. Comments say so, and the rule is disabled at those
two sites rather than left as standing errors.

Also repoints the friendlyFetch pass-through test at rate_limited/429, a code
the server now actually returns, instead of a status/code pair it never sends.

No behaviour change. Kept separate so it can be dropped on its own — it is
unrelated to the assistant service work and was not part of the review that
prompted it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:38:31 +02:00
Eduard GertandClaude Opus 5 2fe0e89acd test(assistant): enforce the server/dashboard tool-name contract
Both registries open with a comment saying their wire names must match the
other's, and nothing checked it. A divergence means the model calls a tool the
dashboard cannot fulfil, which fails at runtime for the user.

The two packages live in one repo, so the test derives the server's names from
the tool directory (mirroring registry.ts's own exclusions) and the dashboard's
from the registry object, and requires them equal apart from an explicit
allowlist — currently just load_skill, which the server executes itself.

Also drops assistant/** from the root eslint config: it is a Bun service with
its own tsconfig and its own CI job, and Next.js rules do not apply to it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:38:20 +02:00
Eduard GertandClaude Opus 5 dfc2c59414 refactor(assistant): restructure into src/ and close the gaps it opened
The service moved from agent/ to src/ and lost its database along the way (the
Drizzle layer, the Prometheus endpoint and the Grafana dashboard are all gone).
The move also dropped things worth keeping, and a full read of the folder turned
up more. This is the restructure plus that pass.

Service layer

- CI: .github/workflows/assistant.yml runs typecheck and bun test on any change
  under assistant/**. The suite had no CI job at all, so every invariant it
  encodes was decoration.
- Rate limiting: src/ratelimit.ts, a token bucket keyed per principal on /v1/*
  and per client IP on /readyz, answering 429 with Retry-After. Restores the
  limiter the restructure deleted. It is in-memory, so the limit is per replica
  rather than per deployment — a deliberate trade for a service with no
  datastore, recorded in a comment and in the README rather than left to be
  discovered.
- Cost telemetry: the chat_turn log line now carries the token usage of all
  three model calls a turn can make (main model, input guardrail, suggestions)
  and the account they are billed to. modelCalls alone could not distinguish a
  200-token turn from a 16k one. A guardrail rejection logs too: it never
  reaches the stream, but the screening call was still billed.
- The verified principal is finally read. It was extracted from the JWT and
  discarded; it now keys the rate limiter and attributes the usage.
- HTTP parsing moved out of the agent layer into the route, so chat() takes a
  validated body instead of a raw Request and MutableCtx collapses into
  TurnContext.
- initAuth dedupes concurrent OIDC discovery behind one in-flight promise, with
  a timeout and a cooldown. Before, a slow or down IdP got one discovery fetch
  per concurrent request, with no backoff and no token required to trigger it.
- The cors rejection code was defined with a user-facing message and never
  used; originGate now returns it instead of bare text. Dropped the unused
  other code, and moved classifyLlmError next to the taxonomy it produces.

PII backstop

- restoreText memoizes its pattern instead of recompiling per stream chunk.
- scrubMessages prefetches the analyzer concurrently and then mints serially,
  so a long transcript is one round of latency rather than one per text part
  and token numbering stays deterministic across resends.
- The analysis cache is keyed by hash with a TTL. It was keyed by the raw text,
  which left up to 500 unredacted messages in memory for the process lifetime
  and contradicted the module's own docstring.

Tools layer

- tools/lib/ became _contract.ts and _schemas.ts, and discovery now skips
  _-prefixed, .test.ts and .d.ts files. A tool's name is its filename, so a
  colocated test file used to become a tool and crash the process at boot.
- The docs extraction and the OpenAPI indexer moved into src/lib/ (docs.ts and
  a new openapi.ts). A tool file's only export should be its defineTool call;
  the tell was tests importing library code out of two of them.
- defineTool accepts a Zod schema and generates the model-facing JSON Schema
  from it, with one uniform rejection in the registry. search_docs, fetch_doc,
  get_api_reference and ask_user were each carrying two hand-maintained copies
  of their contract, and ask_user's had already drifted: the model was told its
  two-to-four option limit in prose and then punished by a validator it could
  not see. render_component and the client tools keep their hand-written
  schemas on purpose.
- The shared input schemas are frozen. They were mutable singletons shared by
  reference across eleven tools.
- Suggestions use generateObject rather than hand-rolled brace-scanning.

Docs, packaging, evals

- A README, which did not exist: request lifecycle, the two-sided tool
  registry, the skill contract, the fail-open consequences of the PII backstop,
  every env var, and an honest limitations section.
- The dockerignore no longer excludes *.md recursively-by-accident-of-syntax,
  the prod compose can find its .env, presidio has a healthcheck the app waits
  on, and both build stages share a base image.
- The evals score tool selection alongside skill routing, with seven new cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 09:38:07 +02:00
Maycon Santos 3be6dda6a7 Load a provider's models from the vendor in the provider modal (#767)
The model picker only offered NetBird's compiled-in catalog, which goes stale as
vendors retire models and cannot reflect what an account is actually entitled to.

Adds a "Load models from provider" action backed by
`POST /agent-network/catalog/providers/models` (netbirdio/netbird#7246). Editing a
saved provider reuses the stored credential, so the masked key never reaches the
browser; creating a new one sends the key being typed. Discovered models merge into
the catalog options, with catalog entries winning a collision because they carry
pricing. A model the backend reports as unpriced is flagged and needs confirmation
before saving, so nothing is registered at a silent zero.

Two implementation notes: the unpriced warning renders once at list level rather
than per row, and the hook uses `ignoreError: true` so a provider-specific failure
is reported inline in the modal instead of as a global toast.
2026-08-23 20:22:35 +02:00
Eduard GertandClaude Opus 5 af7e87456a Align the canvas columns on one vertical line
The cards are not equally tall (group 70px, peer 64, the pickers 66,
policy ~34) and React Flow anchors by the top-left corner, so columns
placed on the same y read as misaligned and policy edges bend. Center
them per node type, and nudge the policy column down to match.

Also matches the group count label to the peer IP size and the left
padding of the peer, peer-picker and group-picker cards to the group
card.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 14:22:30 +02:00
Eduard GertandClaude Opus 5 8c36e192ea Write the shortcut map ref from an effect
Assigning during render is a side effect in the render phase, so a
discarded render could leave the ref holding handlers from a tree that
never committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 14:00:34 +02:00
Eduard GertandClaude Fable 5 7268557f2f Cut comment bloat across the branch
Comments in the touched files go from 4,369 lines to 1,397 (-68%). No code
changed: every file's compiler output with comments stripped is byte-identical
to before, checked across all 139.

Removed comments that restated the line below them, doc blocks repeating a
symbol name and its types, references to callers, change narration, section
markers on a single statement, and facts stated more than once in the same
file — the largest single category, with one file explaining the same cascade
rule four times.

What stayed is capped at a line or two: constraints a reader cannot recover
from the code. The API rejecting bare id strings, never capturing history
mid-drag because the signature stringifies the whole canvas, parents before
children in node updates, the invisible-swap model behind the canvas
transition, and the ordered teardown the setup-key API requires.

Also drops three eslint-disable directives ESLint reported as unused. A fourth,
in useDrillDownBrowserHistory, stays: removing it surfaces a react-hooks/refs
error, because the plugin stops analysing a function once it sees any of its
own suppressions inside it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 12:26:34 +02:00
Eduard GertandClaude Fable 5 90235540cd Fix keyboard nav on unselected lists and draft resource validation
VirtualScrollAreaList starts at index -1 when autoSelectFirst is off, but
the keyboard nav still assumed a non-negative index: Enter called
onSelect(items[-1]) — an uncaught TypeError in the routing-peers popover,
its only such caller — and ArrowUp walked to -2, -3, … Enter is now left
alone when nothing is highlighted, and ArrowUp wraps to the last row.

In the resource modal, the duplicate-name check compared takenNames
case-sensitively while the resourceExists check it is OR'd with is
case-insensitive, so two draft resources differing only in case both
validated and deployed into the very clash the check exists to prevent.

Draft mode also no longer mounts the Access Control tab: this branch
hides its trigger, but the resource-groups callout still linked to it,
and any policy configured there landed in state that saveDraft discards.
The callout now points at the canvas in draft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:34:44 +02:00
Eduard GertandClaude Fable 5 4534589b06 Gate live menu actions on permissions and fix the group rename PUT
liveRenameGroup mapped the group's resources to bare id strings, which
the group PUT rejects ("could not parse json") — so renaming a live group
that owns network resources failed, and because the canvas patch sits
after the await, it silently did nothing. Resources now pass through
untouched, matching useGroupHelper and every other group PUT.

Rename was also offered without permission.groups.update, and the live
resource Edit / Disable-Enable items without permission.networks.update,
unlike every neighbouring action in the same menu. Both now hide when the
role lacks the permission, instead of offering an action that 403s and
appears to do nothing.

Note for later: GroupsProvider.createOrUpdate has the same bare-id
resources mapping, plus a PUT path that interpolates group.id after
resolving groupID by name — both pre-existing on main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:34:32 +02:00
Eduard GertandClaude Fable 5 ff2d54a3ea Fix draft canvas and components panel bugs
- Entities marked for deletion are no longer droppable from the panel.
  Only groups had that guard, so a deleted policy, network or resource
  could be dragged back, drawn fully connected, and deleted anyway on
  deploy. Deleting a network blocks its resources too — the API cascades
  to them, so they would vanish again with no delete change of their own.
- A whitespace-only search no longer empties the category page. Only
  isSearching trimmed the term, so a single space hid every template and
  any entity whose name has no literal space.
- The components panel's onClose is stable, so PanelContent's memo holds
  instead of rebuilding every entity row on each draft-context change —
  even while the panel is closed.
- The selection toolbar recomputes its position when a drag ends. Node
  positions are read imperatively, so nothing else signalled that the
  selection had settled somewhere new and the toolbar reappeared over its
  old spot.
- CanvasToolbar writes through the ReactFlow instance instead of
  useCanvasState, which changes identity on every nodes update — the
  toolbar was re-rendering on every drag tick, which its comment claimed
  to have avoided.
- ToolbarButton renders a shortcut keycap without a tooltip, so the
  selection toolbar's Create Group finally hints its `g` shortcut.
- Framed resource-group rows always mount their edge anchors. A row
  created with showHandles false had no target handle, so a policy
  connection could not be dropped on it and its edges had nothing to
  anchor to.
- A policy's destination scope for an existing-network frame unions the
  API resources with the frame's draft children, which were unselectable
  before.
- A restored network frame orders rows by the network's own resource list
  instead of the global one, so rows no longer reshuffle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:34:20 +02:00
Eduard GertandClaude Fable 5 f206d03799 Fix draft changeset and deploy integrity bugs
Found by the line-by-line review of this branch. Each fix has unit
coverage; the pure helpers gained tests alongside.

- Re-adding a group member the draft had removed now reverts the removal
  instead of also recording an add. The stray add never netted out, so
  the change sat in Review & Deploy forever and deployed a PUT equal to
  live — and a later removal of that member looked like a draft revert
  and vanished silently. The "nothing left to do" rule is now one
  exported predicate (isNoopGroupUpdate) that the trackers and the
  removal cascade share, so they cannot drift.
- The removal cascade prunes an update-group left empty by dropping a
  create-resource or an install-peer, instead of leaving a phantom
  "Update group" row that deploys a pointless GET + PUT.
- Deploy skips already-sent changes by payload, not just by id. The
  changeset coalesces later edits into the same entry, so an edit made
  after a partial failure was never sent and was then discarded with the
  changeset. Completed creates are still skipped unconditionally — the
  entity exists, so a second POST would duplicate it.
- undo/redo cancel the armed debounced capture before flushing. Only the
  effect cleanup cleared it, which React runs in a later task, so an
  expired timer could fire after undo() rewound the stacks and wipe redo.
- An update-group's resources render in the same wire shape on both sides
  of the Review & Deploy diff, so unchanged members no longer show as
  removed + added.
- Removing a draft group also drops its stale SSH authorized_groups key,
  which is name-keyed and would otherwise be sent as if it were an id.
- Copy cURL escapes apostrophes so the body stays pasteable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:34:01 +02:00
Eduard GertandClaude Fable 5 b1f7928861 Restore the Agent Network overlay in the control center
The page.tsx teardown into view hooks dropped the agent-network overlay:
addAgentNetworkProviderNodes, the agentPolicies fetch, the layout buckets
for both node types and the agent-policy click handler all went with it,
orphaning AgentPolicyNode and ProviderNode. The feature itself is still
live and account-gated, and nothing recorded a decision to retire it.

Rebuilt for the current architecture rather than transplanted:

- views/agent-network-overlay.ts holds the pure builder that appends
  source -> agent-policy-<id> -> provider-<id>, plus the small hook that
  supplies its data.
- Data comes from useAIProviders() instead of a second set of fetches.
  The provider already owns those endpoints and already gates them on the
  feature flag, which also drops the API-to-domain cast the old code
  needed.
- The group and peer views call it where they did before; the peer view
  unions across the peer's groups.
- Agent policies rejoin the policy column, providers the destination
  column, and clicking an agent policy opens AgentPolicyModal through the
  new openAgentPolicy on the policy context.
- Edges use "smart", matching the rest of the live views. SmartEdge reads
  data.policy optionally, so an agent edge without one is fine.

With the feature disabled the provider issues no requests and the overlay
is a no-op, so the views behave exactly as before.

This commit also carries useSelectNodeHandlers' share of the preceding
cleanup pass — its hunks interleave with the click-handler change too
closely to split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 09:48:36 +02:00
Eduard GertandClaude Fable 5 e6bb6827f2 Fix deleting several draft groups at once
deleteGroup ran once per selected group, and each run rebuilt the policy
payload from a canvas that had not yet absorbed the previous run's patch.
Since trackUpdatePolicy supersedes the entry for a policy, the group
removed first reappeared in the payload written by the second, and its
DELETE was rejected at deploy.

The batch is now resolved in one pass: every group is folded out of each
policy before a single update is tracked per policy, and the deletes,
canvas-instance removal and node/edge updates all happen once.
confirmAndDeleteGroups hands the whole selection over; the single-node
deleteGroup wrapper had no callers left and is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 09:48:15 +02:00
Eduard GertandClaude Fable 5 9430168c51 Remove dead code and comment bloat across the control center
A line-by-line pass over the draft-mode branch. Every candidate was found
by one review agent and re-verified by a second before being applied.

Dead code: applyD3ForceLayout and the d3 import, getDraftWarnings,
addExpandedGroupContent, the policy-drop-position mechanism, the
unreachable live branch of useCreateGroupOnCanvas, NetworkNode's
non-frame card and its /networks/resources fetch, six unreachable
PolicyNode handles, the ChangeCodeView header path, SetupModal's
ephemeralKey prop chain, the .cc-focus-root rules, and never-passed
props on several components. Deletes DraftModeTitle, NodeItem, NodeHalo
(never imported) and BidirectionalEdges, FloatingEdge (registered in
EDGE_TYPES but no code ever creates those edge types).

Comments: corrected the ones that described code that had moved on, and
dropped the ones that only restated what the line below already said.

De-duplication: the group/peer panels now share their placement and
close-guard plumbing, PeersToolbar reuses the node-capabilities set, the
draft uid helper and the resource/placeholder icon maps have one home
each. vitest.config.ts becomes .mts so Vite's native loader parses it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 09:47:41 +02:00
Eduard GertandClaude Fable 5 cb5736906c Remove a deleted group from referencing policies in draft
The API rejects a group DELETE while a policy still references it, so
deleting an existing group in draft now records an update for every
tracked policy that references it (via the new removeGroupFromPolicy
helper) before scheduling the delete, and patches the canvas policy
nodes to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:31:45 +02:00
Eduard GertandClaude Fable 5 5dd1460628 Add a feedback button to the control center
Bottom-right corner of the canvas, styled like the draft-mode Cancel
button, opens the feedback form in a new tab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:08:50 +02:00
Eduard Gert 95cd9d7de7 Merge remote-tracking branch 'origin/main' into feature/control-center-draft-mode
# Conflicts:
#	src/utils/version.test.ts
2026-08-19 14:16:52 +02:00
Eduard GertandClaude Fable 5 9b5b1a7a55 Drop the next/dist imports from the navigation guard
Patch the same shared app-router instance as before, but reach it via
window.next.router (the object AppRouterContext provides) instead of
importing the context from next/dist; the router type comes from the
public useRouter return type. If Next ever drops that handle, the guard
logs an error and the draft-leave e2e test fails. Install and guard
registration are now one-liner hooks: useNavigationGuard() in the
layout, useSetNavigationGuard(guard) in DraftLeaveGuard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:57:45 +02:00
Eduard GertandClaude Fable 5 4467d42036 Fix setup modal reopening and blocking the app after a deploy
Closing the first-run Install NetBird modal never cleared the
netbird-first-run flag (AddPeerDropdown dropped the reset the old
AddPeerButton had), so any remount of the peers empty state — like
exiting draft mode after a successful deploy — auto-opened it again
in test builds, and its overlay swallowed all navigation clicks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:07:14 +02:00
Eduard GertandClaude Fable 5 d4b401dab8 Keep installed placeholder peers in the changeset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:06:09 +02:00
Eduard GertandClaude Fable 5 0985fa388e Fix install-key state for placeholders absorbed into a group
The group panel's Install chip stayed "Install" after a setup key was
generated, and reopening the modal (from the panel row or a Review &
Deploy issue badge) offered "Generate Key" again — minting a second key.
The chip now flips to "Waiting" and both paths reuse the stored key via
getPlaceholderSetupKey, which also reads a group's draftPeers entry.
getPlaceholderPeer carries the install artifacts so absorbing an
installed placeholder no longer strips them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:05:25 +02:00
Eduard GertandClaude Fable 5 0b70c56b1b Warn before leaving draft mode via programmatic navigation
Since Next 16, useRouter() returns a per-component copy of the router,
so DraftLeaveGuard's patching of its own copy never intercepted the
sidebar or profile dropdown. Wrap push/replace once on the shared
AppRouterContext instance (installed in DashboardLayout before the
chrome renders) and let DraftLeaveGuard register a guard that shows the
same discard confirmation as Cancel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 12:54:09 +02:00
Eduard GertandClaude Fable 5 71a98c4ccf Make the control-center header responsive
The header's top-left and top-right clusters were independent absolute
layers, so on narrow screens the network-view controls slid under the
Live/Draft switcher. They now share one row: the switcher stays pinned
top-right (stacking Live/Draft above Cancel/Review below lg), while the
left controls wrap as logical groups and the network selector shrinks
instead of overflowing. The views switcher renders as a select below lg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 11:43:42 +02:00
Eduard GertandClaude Fable 5 b21b38d17e Fix placeholder peers vanishing from a group created via selection
Create-group-from-selection only stored placeholder peer ids in
addedMembers, never their objects as draftPeers on the group node —
unlike drag-to-group. The Details panel therefore resolved zero members
(placeholders aren't in /peers), and its close cleanup wrote that empty
count back onto the node, so the group showed "No Peers" after opening
the panel even though the changeset still held both peers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 11:07:28 +02:00
Eduard GertandClaude Fable 5 bf04af5416 Fix network delete from the live drilled-down header
The live single-network view renders only the network's policies,
groups, and resources — no network-<id> node — so useDeleteNetwork's
canvas lookup came up empty and the header's Delete bailed out before
the confirm dialog. Fall back to resolving the network from /networks
by the id embedded in the node id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 10:58:27 +02:00
Eduard GertandClaude Fable 5 c77aba6c44 Fix routing peer prefill when editing a router
The edit modal initialized its peer selection from the async SWR fetch
via useState, which only reads its argument on first render — so on a
cold cache the prefill never appeared. State now holds only an explicit
user selection (null = untouched) and falls back to the fetched peer
during render; the selector is disabled while that fetch is in flight.
PeerSelector's onChange is narrowed to a plain callback so state setters
of wider types remain assignable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 10:48:51 +02:00
Jack Carter 98ae943bbc feat: add required Country field to Agent Network signup form (#766)
The HubSpot form behind the self-hosted Agent Network signup now has a
required company-scoped Country/Region field, so submissions without it
are rejected. Adds a searchable Country dropdown to the signup step and
includes the value as an objectTypeId 0-2 field.

Uses a static country list instead of /locations/countries since the
geolocation database may be absent on self-hosted deployments and the
HubSpot property expects full country names, not ISO codes.
2026-08-19 10:37:13 +02:00
Eduard GertandClaude Fable 5 ec7762ab91 Fix ctrl+scroll zoom in draft select mode
With the select tool active the canvas uses panOnScroll, and xyflow
routes ctrl+wheel (and trackpad pinch) through zoomOnPinch instead of
zoomOnScroll. Gating zoomOnPinch on canInteract therefore made zooming
impossible in select mode, so loosen it to menu/empty-state locks only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 10:18:20 +02:00
Misha Bragin 99f86b6421 fix(setup): match the v0.75 client UI in the management URL step (#765)
The desktop client reworked its UI in v0.75: the management server is now
  asked for on the first-run "Set up NetBird" screen and lives under
  Settings > General, so the "Settings > Advanced Settings" path the Windows
  and macOS tabs described no longer exists. Move the copy into a shared
  ManagementUrlStep, cover the already-installed case, and say "menu bar"
  instead of "system tray" on macOS.

  Fixes #732

* Remove unnecessary comment
2026-08-18 16:01:58 +02:00
Eduard GertandClaude Fable 5 feb60d9e78 test(assistant): integration tests against a real Presidio analyzer
Cover the wire contract the mocked unit tests can't; self-skipping when
the analyzer sidecar isn't reachable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 13:25:32 +02:00
Eduard GertandClaude Fable 5 c47de2f864 feat(assistant): flesh out and restructure the system prompt
Add domain knowledge sections (peers, access control, routing, reverse
proxy, DNS, team, activity, settings), fold the control-center modeling
rules into them, list the full tool roster, and restyle per Anthropic
prompting guidance for Sonnet 5 (parallel tool calls, no aggressive
emphasis).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 13:18:10 +02:00
Eduard GertandClaude Fable 5 d20ea657e5 feat(assistant): client-side redaction and scalable tool reads in the dashboard
- one Redactor engine (utils/redaction.ts): whitelist redact(value, config),
  detect() over the name catalog + PII patterns, restore/resolve for tokens;
  per-tool whitelists live at the tool definitions; square-bracket tokens
- tool executor computes filters/time ranges/search/sort/count_by over the
  full fetched list, then caps rows and bytes behind an explicit truncation
  envelope with parameter validation and applied-param echo
- UI: always-mounted message actions (no layout shift, per-message copy
  disable), streaming-safe inline components, OSLogo size prop for the context
  chip, "Ask" header button, centered stop icon, empty-network callout in the
  draft destination modal
- remove dead code: models flow, feedback theatre, source list, history panel,
  legacy interfaces

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 10:14:10 +02:00
Eduard GertandClaude Fable 5 df6825ca99 feat(assistant): rebuild service on UI-message stream with Presidio PII backstop
Rework the vendored service around the AI SDK stream (agent/) and make the
privacy boundary two-layered:

- always-on server-side PII backstop: Presidio analyzer sidecar (pinned image,
  internal-only), per-request vault minting [REDACTED_*] tokens, streaming
  restore with split-token hold-back, LRU'd analysis, circuit breaker, fail-open
- list tools accept filters/since/until/query/sort_by/order/offset/limit and
  count_by so big accounts are answered by narrowing, not raw row dumps
- add get_current_user; switch tokens to square brackets; draft-first prompt
- drop the dead /v1/models flow, mutating-tool remnants, unused headers/params,
  test-only exports, and the @anthropic-ai/sdk dependency

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 10:14:10 +02:00
Viktor Liu 0ceda40180 Show the SSH enable commands with the privileges the daemon now requires (#737) 2026-08-16 19:29:16 +02:00
Misha Bragin f4cf7458be fix(agent-network): show real provider name in access log (#758) 2026-08-16 16:44:16 +02:00
Misha Bragin 9e1c29b769 fix(version): ignore build metadata when detecting available updates (#759) 2026-08-14 14:29:42 +02:00
Eduard GertandClaude Opus 5 249550116c feat(assistant): rate-limit /readyz per client IP
The probe is unauthenticated, so the existing rateLimit middleware can't
cover it — that one keys off ctx.principal, which a probe has no way to
supply (reusing it would dereference undefined and 500). This adds a
per-address token bucket, checked before readyz() so a flood is refused
ahead of the DB check rather than after it.

The ceiling is deliberately far looser than the chat limit (100 per 10s vs
30 per 60s): a load balancer polls every few seconds, and refusing a real
probe marks a healthy instance down — worse than the flood it prevents.
Buckets are per address, so one flooder cannot refuse the balancer, and
lapsed buckets are swept once the map is large enough for a scan to pay.

Client IP is the socket address. X-Forwarded-For is caller-supplied and
only honoured under TRUST_PROXY_HEADER (default off): trusted while
directly exposed, it would let a flood mint a fresh bucket per request by
rotating the header, or get a peer refused by forging theirs.

withMetrics now forwards Bun's server argument so a handler can reach
requestIP(); it stays optional, as handlers that don't need it declare one
parameter.

Verified against a running server: with the ceiling at 5, requests 1-5
return 200 and 6+ return 429 with Retry-After; the refusal keeps its CORS
headers, so the dashboard's browser probe reads a 429 rather than an opaque
network error; /healthz is unaffected; and 30 back-to-back probes at the
defaults draw no refusals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 16:04:59 +02:00
Eduard GertandClaude Opus 5 794cd67813 fix(assistant): cache the readiness DB check
/readyz is unauthenticated, CORS-enabled and un-rate-limited (rateLimit is
only composed onto /v1/*), so every probe ran a fresh `SELECT 1`. That made
it a request amplifier: a flood from one host turned cheap HTTP requests into
DB round trips and could exhaust the connection pool the chat path needs —
taking the service down through the endpoint that reports whether it is up.

Cache the result for 2s and share the in-flight check between concurrent
callers. Probes run every few seconds, so a result this stale is
indistinguishable to them.

Measured with statement logging on the dev Postgres, before -> after:
  3 rapid requests    3 queries -> 1
  200 concurrent    123 queries -> 1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:57:56 +02:00
Eduard GertandClaude Opus 5 dc989324f9 feat(assistant): vendor the netbird-assistant service into the repo
Moves the standalone netbird-assistant project (previously a separate
WebstormProjects checkout with no commits and no remote) under assistant/
so the backend service lives alongside the dashboard that talks to it.

node_modules is not carried over — reinstall from bun.lock. The service's
own .gitignore comes along, which keeps .env untracked now that the
directory sits inside a repo whose root gitignore only covers .env*.local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:46:23 +02:00
Eduard GertandClaude Opus 5 e50f1f6393 Merge branch 'feature/control-center-draft-mode' into feat/ai-assistant
Conflicts resolved:
- eslint.config.mjs: both branches added it independently; kept the union of
  ignore patterns and both branches' rule setup.
- src/layouts/Navigation.tsx: kept the absolute-positioned collapsed rail —
  fixed positioning escapes the dashboard card's overflow once the assistant
  panel insets it.
- src/layouts/DashboardLayout.tsx: kept the assistant-panel layout, carrying
  over the keyed page Fragment from the other branch.
- package-lock.json: regenerated against the merged package.json (next 16.3.0
  plus the assistant-ui dependencies).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:20:29 +02:00
Eduard GertandClaude Opus 5 f378630f11 chore: refresh package-lock after merging main
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:13:17 +02:00
Eduard Gert 39c4d44553 Merge branch 'main' into feature/control-center-draft-mode
# Conflicts:
#	package-lock.json
#	package.json
2026-08-13 15:10:53 +02:00
Eduard GertandClaude Opus 5 5f85a7d12c feat(assistant): canvas agent — routing peers, resource groups, typed identifiers
Assistant + control-center work on top of the draft-mode canvas.

Control center
- A placeholder peer can be a network's ROUTING PEER: the routing-peer modal
  lists the draft's placeholders (exempt from the version gate — an uninstalled
  peer has no version), the router change is recorded against its draft id, the
  peer's own install-peer step blocks the deploy, and installing rewrites the
  router with the real id. The request preview renders {x_peer_id} rather than an
  unrunnable draft id.
- Grouping a resource that lives in a network frame keeps its card in the frame:
  the row is the network's representation of it, and removing it left the frame
  looking empty while the changeset still deployed the resource into it.
- The group panel offers canvas-drawn resources as well as API ones, mirroring
  what it already did for placeholder peers.
- Assistant actions: route_network, new_resource_group (a group inside a frame),
  and initial `members` on a new/existing group. Membership goes through one
  resolveMember — a draft resource's node data holds no id (it comes from
  getDraftResource), so reading the raw field made grouping a no-op that still
  reported success.
- The canvas is locked for the whole assistant TURN, not per step: a step lasts
  milliseconds and the gap between two of them seconds, so per-step scoping
  handed the canvas back mid-layout. The overlay is silent — the panel narrates.

Assistant
- Identifiers the user types are pseudonymised into the SAME tokens as the data
  (names from the loaded lists, addresses and emails by pattern), so "where is
  100.84.175.167?" is matchable instead of reaching the model as a value it can
  never line up with. Values that can be attributed carry a note naming the
  resource they belong to. This also stops typed addresses leaving the browser.
- The status line says what the assistant is doing — the running step's own
  label, or its reasoning condensed to one short phrase. Reasoning itself is
  never rendered in the thread.
- open_page refuses a scalar token as a resource id instead of navigating to a
  dead end, and the activity row shows the route it actually pushed.
- Modals stay inside the dashboard card while the assistant panel is open, and
  the canvas pans out of the panel's way (only as far as it must).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 14:29:50 +02:00
Eduard GertandClaude Opus 5 fb05f2c172 chore(docker): upgrade base image to Alpine 3.24 (#756)
Alpine 3.14 reached end of life in May 2023 and has received no security
fixes for roughly three years. nginx is installed from the Alpine repos
rather than pinned, so the base bump also moves nginx 1.20.2 -> 1.30.4,
certbot 1.16.0 -> 5.6.0 and supervisor 4.2.2 -> 4.3.0.

Alpine 3.24 carries security support until June 2028.

Verified by building and running this Dockerfile on 3.14, 3.23 and 3.24 and
comparing: the served index.html is byte-identical across all three, the CSP
response header hashes identically, `nginx -t` passes on the existing config,
and supervisord reaches the same state. /etc/crontabs/root and
/etc/nginx/http.d/ still exist on 3.24, so the `rm /etc/crontabs/root` step and
the config COPY paths are unaffected. Every certbot flag used by init_cert.sh
is still accepted by certbot 5.x.

Certificate issuance itself could not be exercised without a real domain, so
the certbot 1.x -> 5.x jump should get one staging issuance before rollout.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:12:03 +02:00
Eduard GertandClaude Opus 5 6cf828ce8a Upgrade Next.js to 16.3 (#755)
* chore: upgrade to Next.js 16.3

Turbopack is now the default for both `next dev` and `next build`, so the
separate `turbo` script is redundant and has been dropped.

`next build` rewrites tsconfig `moduleResolution` to "bundler", which Next 16
expects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: migrate ESLint to flat config

`next lint` was removed in Next 16, so `npm run lint` had been failing with
"Invalid project directory provided, no such directory: ./lint" since that
upgrade. eslint-config-next 16 also ships flat config only, which the old
.eslintrc.json could not consume.

Ignores .claude/ so stray worktree copies of the repo are not linted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: replace ip-cidr with ip-address to clear CVE advisories

ip-cidr pulled a vulnerable ip-address (GHSA-mwp4-54f8-5fhr, high: leading-zero
octets decoded as decimal while resolvers read them as octal, enabling SSRF;
GHSA-v2v4-37r5-5v8g, moderate: XSS in Address6 HTML methods). npm reported "no
fix available" because even the latest ip-cidr pins ip-address ^9.0.5, still
inside the vulnerable range.

ip-address 10.x was already a direct dependency and covers every use: its
isValid rejects leading-zero octets and validates mask ranges natively, and
isInSubnet replaces the one `new cidr().contains()` call site.

The new validators were differential-tested against the real ip-cidr over a
67-input corpus (134 checks) before removal, with no divergences; containment
was checked across 14 v4/v6/boundary/cross-family cases. `npm audit` now
reports 0 vulnerabilities.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: replace next-themes with an in-repo ThemeProvider

next-themes renders an inline <script> to set the theme before paint. It was
loaded through dynamic(ssr: false), so that script rendered on the client, where
React 19 never executes script tags and warns:

  Encountered a script tag while rendering React component.

Upgrading would not have helped; 0.4.6 still renders the script
unconditionally. The script was also useless here: injected after hydration, it
could not prevent a flash, and postbuild.js hoists inline scripts out of the
export anyway.

The replacement keeps the same `netbird-theme` storage key and three-mode
support, and sets the theme class on <html> in the layout instead, so the
static export ships the correct theme in its first byte with no script at all.

Dropping the dynamic(ssr: false) wrapper removed an implicit prerender bailout
for the whole provider tree, which exposed isNetBirdCloud() reading
window.location during the build-time render; it is now guarded. The exported
HTML is byte-identical either way, since the page already bails to client
rendering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: key the router children slot in the dashboard layout

React logged "Each child in a list should have a unique key prop. Check the
render method of DashboardPageContent." The App Router's segment element for
`children` arrives with a null key, which React flags once it is reconciled
alongside the Navigation sibling. Wrapping it in a keyed Fragment resolves it.

Verified in the browser both ways: with the Fragment the console is clean,
without it the warning returns.

Also applies the pending simple-import-sort fix for this file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: match IPv4-mapped IPv6 hosts against IPv4 CIDRs

Regression from replacing ip-cidr. Its contains() normalised an IPv4-mapped
IPv6 address to the embedded v4 before the subnet check, so
contains("::ffff:10.0.0.5") on 10.0.0.0/24 returned true; isHostInCIDR returned
false. Verified against ip-cidr@3.1.0 directly.

This is reachable: isValidIP accepts "::ffff:10.0.0.5", so a mapped host entered
in ReverseProxyAddressInput against an IPv4 resource CIDR was wrongly rejected.

An IPv6 network still rejects a mapped host, so families do not leak. Checked
across 20 cases (6 mapped-host, 14 pre-existing), with the mapped expectations
taken from measured ip-cidr output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: derive skeleton colors from the resolved theme

SkeletonTheme had the dark palette hardcoded, so a light theme would render
dark skeletons. The light values are the ones already defined on the
feature-light-theme branch rather than new invented colors.

resolvedTheme is tracked in state and updated from the existing apply(), so the
palette also follows the OS preference under "system".

Latent today, since DarkModeToggle disables both Light and System.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:11:48 +02:00
Eduard GertandClaude Opus 5 83cb07a44c Fix icon alignment in collapsed navigation and network tab Add buttons (#748)
* fix: center icons in collapsed navigation rail

Nav items have 24px of left padding (px-3 on the li plus px-3 on the
button), so with 16px icons the icon center lands at 32px. The 70px rail
centered at 35px, leaving icons 3px off. Narrow the rail to 64px so the
two line up, and match the content offset in PageContainer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: use one plus icon across network tab Add buttons

The Resources and Routing Peers Add buttons used tabler's IconCirclePlus
while the Services tab used lucide's PlusCircle. Both were size 16, but
the two icon sets draw at different inner scales, so the glyphs read as
different sizes. Standardize on lucide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:11:33 +02:00
Eduard Gert fd0f613674 Merge branch 'feature/control-center-draft-mode' of https://github.com/netbirdio/dashboard into feat/ai-assistant
# Conflicts:
#	package-lock.json
#	package.json
#	src/app/globals.css
2026-08-12 10:05:19 +02:00
Eduard GertandClaude Opus 5 4722d19ad6 feat: add AI assistant panel
Adds the assistant side panel: chat runtime, streaming API client,
client tools, route-aware context, name redaction, chat history and a
standalone preview route. Migrates ESLint to flat config.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 10:01:56 +02:00
Viktor Liu bbfa2d3a79 Bump default wasm client to v0.76.3 (#754) 2026-08-11 17:17:00 +02:00
Brad Ison a92816b9b8 Bootstrap Agent Network settings via the explicit POST (#746)
Adopts the new Agent Network settings contract:

- The management API replaced the implicit settings bootstrap — a
  `bootstrap_cluster` hint on the first provider create — with an
  explicit POST /agent-network/settings carrying the proxy cluster
  address. The settings response now carries endpoint +
  proxy_address + dedicated instead of cluster + subdomain.

- The provider wizard keeps its UX (auto-picking a validated cluster,
  gating the first create on having one) but now bootstraps the
  settings row with the explicit POST right before the first provider
  create. A bootstrap failure keeps the wizard open and surfaces the
  error — previously a failed bootstrap was swallowed server-side and
  the account silently ended up with a provider but no endpoint. A 409
  (concurrent bootstrap won) is treated as success, since the row
  exists either way.

- Types updated accordingly; bootstrapAgentNetworkSettings is exposed
  on the providers context. The onboarding provider step reuses the
  same wizard, so it is covered by the same change.

Without this, the dashboard against a new management silently stops
bootstrapping accounts: provider create succeeds (the old hint is an
ignored unknown field), but no endpoint is ever assigned.
2026-08-11 11:32:27 +02:00
Bethuel Mmbaga 0caf754897 Update Entra ID SCIM setup wizard for the new Azure portal UI (#750) 2026-08-10 11:12:40 +02:00
Misha Bragin 49582fff74 Trim numeric build suffix from sidebar version display. (#749)
Self-hosted management versions can carry a numeric build suffix
  (e.g. 0.76.3-31256681241) that overflows the sidebar box. Show the
  semver only and move the full string into the hover tooltip alongside
  the latest version. Pre-release labels like -rc.1 are preserved.
2026-08-09 19:20:49 +02:00
Eduard GertandClaude Opus 5 af1e0ee4af fix: use one plus icon across network tab Add buttons
The Resources and Routing Peers Add buttons used tabler's IconCirclePlus
while the Services tab used lucide's PlusCircle. Both were size 16, but
the two icon sets draw at different inner scales, so the glyphs read as
different sizes. Standardize on lucide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 15:46:25 +02:00
Eduard GertandClaude Opus 5 7dc69d02de fix: center icons in collapsed navigation rail
Nav items have 24px of left padding (px-3 on the li plus px-3 on the
button), so with 16px icons the icon center lands at 32px. The 70px rail
centered at 35px, leaving icons 3px off. Narrow the rail to 64px so the
two line up, and match the content offset in PageContainer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 15:43:01 +02:00
Eduard GertandClaude Opus 5 41e0ae3eb3 Stop creating ephemeral setup keys for agent placeholders
The draft Agent template's setup key was flagged ephemeral purely because the
template's own description mentions the word — nothing in the API contract or
product asks for it, and every other setup-key path in the dashboard sends
ephemeral: false. An agent placeholder becomes a real part of the deployed
topology (groups, policies, sometimes a router), so having the management
service delete it after ~10 minutes offline would silently break the config
the user just deployed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:51:30 +02:00
Eduard GertandClaude Opus 5 996a2bc4e7 Tear down draft placeholder artifacts in dependency order
The bound group could not be deleted while the setup key still listed it as
an auto_group (the API rejects linked groups), and both deletes were fired in
parallel, so the group often survived on the freshly registered peer.

Sequence it instead, still silent and best-effort: unlink the group from the
key's auto_groups, empty the group's peers, delete the group, then delete the
setup key, then revalidate /peers + /groups so the matched peer drops the
throwaway membership without any user-facing step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:51:30 +02:00
Eduard GertandClaude Opus 5 4381a90e45 Give draft destinations a real column in frames drafts
Auto Arrange left a policy's destination on the SOURCE side whenever the
canvas also carried a network frame: the destination restack lived in the
frameless branch only, so in a frames draft the hierarchical layout's
type buckets had the last word — a destination peer stayed stacked on the
sources at x 0, and a standalone resource landed inside the frame grid,
to be shoved aside by the overlap pass. A "Server -> policy -> Agent"
draft on a networks canvas came out with both peers on the left.

Hoist the destination restack out of that branch so it runs for every
draft, and start the frame grid past the destination column when both
exist (frames sit at 1050, the column at 1000). With frames as the only
destinations the grid keeps its bare FRAME_GRID_BASE_X, so live parity on
the networks overview is unchanged.

Also re-center the source column whenever it is non-empty: pulling a
destination peer out of the shared peerNode bucket used to leave the lone
remaining source at the pair's top offset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:48:53 +02:00
Eduard GertandClaude Opus 5 98fef0f82a Fix the three failing control-center/team e2e specs and drop text-matched selectors
The report's hard failure was a missing test id: submit-policy existed only on
the create modal's last step, so the draft policy edit test could never save.
Its three follow-up tests had been skipped by that failure and were each broken
in turn — a renamed menu item, a reload that assumed the canvas after the API
helper had navigated away, a components-panel search cleared by the category
switch (leaving the row unmounted below the fold), and an afterAll that spun up
a fresh context and outran its own timeout.

The drilldown flake was a client-side route update from the previous test
aborting the next navigation; navigateTo now retries once on ERR_ABORTED.

The Billing Admin flake was a real cross-spec leak: the control-center helper
pinned netbird-test-edition=licensed with an init script on the worker-shared
owner page, and Billing Admin is cloud-only — so whether it appeared depended
on file ordering. The edition is now set per navigation, and the billing test
asks for the edition it needs.

Selectors in the control-center suites go through test ids instead of copy:
cc-menu-<action> / cc-canvas-menu-<action> on the context menus, and title ids
on the policy modal and the shared confirmation dialog.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 12:47:37 +02:00
Eduard GertandClaude Opus 4.8 e90fb6f4cb Show new control-center resources instantly and skip create-flow animation
After creating a resource in the live drilled network view, rebuild the
canvas from the revalidated data so the resource appears without a
re-navigation. Drilling into a just-created network now switches
instantly — no drill transition, fitView tween, or empty-state reveal —
so the flourish doesn't play behind the add-resource prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 11:40:00 +02:00
Eduard GertandClaude Opus 4.8 ec3949d0dc Animate control-center camera on first open
The first fit after mount snapped instantly to avoid an origin flash, so the
peer view popped into place with no fly-in. Fit while the viewport is hidden,
snap to a zoomed-out start, reveal, then animate in on the next frame.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 10:47:23 +02:00
Eduard GertandClaude Opus 4.8 31a7fea6bf Revert e2e workflow to single-job; keep the test fixes
Sharding the suite across a matrix would run create-test-env.sh once per
shard, which is the expensive part — and on GitHub-hosted runners the only
ways to avoid that (shared remote backend, or snapshot/restore + build-once)
add real complexity. Not worth it right now: keep one env + one build.

Restore .github/workflows/e2e-test.yml and e2e/playwright.config.ts to their
pre-sharding single-job form. The genuine improvements from that work stay:
the draft inline-group hard-fail fix and the three flaky-test hardenings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 10:05:08 +02:00
Eduard GertandClaude Opus 4.8 ebf44cb5bf Use fresh /groups peer counts in control-center live views
Peer/group/user live views built group nodes straight from the
policy-embedded group objects (rule.sources/destinations), whose
peers_count is a snapshot from when /policies was fetched and goes stale
as peers join/leave groups. The side panel already reads /groups, so the
canvas label and the panel disagreed.

Add withFreshGroupCounts() to override peers_count/resources_count from
the authoritative /groups list (already fetched by useControlCenterData),
falling back to the embedded snapshot for draft-only groups, and apply it
at every group-node build site in the three live views.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 09:49:20 +02:00
Eduard GertandClaude Opus 4.8 0e5ac764cd Harden three flaky e2e tests at their racy step
All three passed on retry in CI; each raced a known timing window:

- control-center-live "delete a policy": the seeded policy node was
  occasionally not placed after navigating to the group view (stale SWR
  read / build-fit race). Re-open the view until the node is on canvas.
- team-service-users "update admin role": the list served a stale role
  cell right after a role change + navigation. Wait, then reload once if
  it hasn't settled (read-only verification, so a reload only re-reads).
- team-users-approval-and-billing "Billing Admin": the Radix Select open
  was swallowed so the item never rendered. Retry the trigger click until
  the item is visible (same pattern as the matrix spec).

None weaken the assertions — roles, node presence, and saves are still
verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 09:48:05 +02:00
Eduard GertandClaude Opus 4.8 1e2039c12b Shard e2e workflow and fix draft inline-group test
Fix the one hard failure in control-center-draft-connections (inline group
in the policy modal): the naive click→fill→Escape on the destination group
selector let a still-animating Radix popover swallow the trigger click, so
the search input never appeared. Adopt the retry-until-visible + in-modal
close pattern already proven in the matrix spec. This also un-skips the 4
serial tests that were skipped behind it.

Split the Playwright workflow into build → 4 shards → merge:
- build the static app once and pass out/ to shards as an artifact
- each shard boots its own docker env (isolated account) and runs a
  --shard slice; fullyParallel:false keeps serial specs whole and per-shard
  backends stop cross-file account state from colliding
- merge-report stitches shard blobs into one HTML report + summary and
  gates on both the matrix result and the merged failure count

Switch the CI reporter to blob to enable cross-shard merging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-07 09:42:44 +02:00
Eduard GertandClaude Opus 4.8 2d00618483 Scope Review No Network check to the resource change
"No Network" also renders on the canvas resource node, so scope the assertion
to the resource change row (cc-change-create-resource) inside the Review modal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 18:09:09 +02:00
Eduard GertandClaude Opus 4.8 b6eb3eec20 Disambiguate Review Install locator (badge vs expanded button)
"Install" matched both the issue badge and the expanded change's cc-peer-install
button. Target the cc-peer-install testid for the peer step and the unique
No Network text for the resource.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 18:02:27 +02:00
Eduard GertandClaude Opus 4.8 73d46a40ea Assert Review badges, not unrendered warning texts
getCanvasWarnings' detailed messages ("references a peer that isn't installed
yet", "has no network assigned and won't deploy") are dead code — the Review
modal surfaces blockers as badges (Install / No Network) via getChangeIssue.
Assert those badges, which is what the test's intent verifies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 17:54:30 +02:00
Eduard GertandClaude Opus 4.8 395c6e8c36 Make column-join assertion relative to the other column
Comparing the joined group's x to the anchor within 1px was fragile in-suite.
Assert it settled nearer its own column's anchor than the other column, which
is what "joined the column" means and tolerates layout offsets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 17:32:40 +02:00
Eduard GertandClaude Opus 4.8 13c671a9fb Fix policy-edit test save button and settle column positions
- The draft-policy edit modal saves via "Save Changes" (update flow), not the
  create flow's submit-policy button (that lives on the general tab).
- Poll the joined groups' positions until layout reconcile settles them into
  the existing column, instead of reading once right after save.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 17:24:29 +02:00
Eduard GertandClaude Opus 4.8 c312761b68 Harden context-menu and policy-edit selector interactions
- clickContextMenuItem: retry the whole open+click; the menu re-renders and its
  items detach when the node underneath updates.
- Policy-edit group selectors: retry opening each side's popover, and close it
  with an in-modal click instead of Escape (Escape can close the whole modal
  once the popover is already gone).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 17:03:23 +02:00
Eduard GertandClaude Opus 4.8 89326a1888 Make policy-edit group-selector loop wait for popover open/close
Opening the destination group selector right after the source one closed had
its trigger click swallowed by the still-closing Radix popover. Wait for each
side's search to be visible after opening and hidden after Escape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 16:45:28 +02:00
Eduard GertandClaude Opus 4.8 f09fed1486 Give focus-dimming test a second policy so Focus is offered
isFocusWorthy requires 2+ policies on the canvas, so the Focus menu item never
appeared with the test's single policy. Add a second off-path policy (a peer
connected to g2) — that peer/policy is also what focus dims.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 16:32:07 +02:00
Eduard GertandClaude Opus 4.8 8e7d5308b9 Fix flaky/stale matrix tests: Backspace removal, drop-ring
- Backspace-to-remove: wait for the click-selection to register, then blur the
  panel's focused search input (React Flow ignores delete keys while an input
  is focused) instead of relying on Escape keeping selection — removes a race.
- Drop-ring: only the group under the cursor highlights now (dropTarget ring);
  drop the stale "all eligible groups show border-white/60" mid-drag assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 16:21:40 +02:00
Eduard GertandClaude Opus 4.8 ac39c5f78c Prime API token before draft in matrix beforeEach
getApiContext()'s first read navigates to /team/users to capture a Bearer
token; a mid-test listGroups() therefore navigated off the canvas and dropped
draft mode. Prime it in beforeEach (before enterDraft) so the token is cached
and later listGroups() calls don't navigate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 15:37:41 +02:00
Eduard GertandClaude Opus 4.8 83e8db5d79 Fix control-center matrix e2e: resource drops, network connects, canvas hook
The matrix suite never ran end-to-end (it was blocked at the first test by the
components-panel category issue). Getting it running surfaced several latent
mismatches with current behavior:

- place(): a resource drop opens the editor modal (an address is required), so
  fill name+address and submit to create the node; dragTemplateToCanvas no
  longer presses Escape for resources (it would dismiss that editor).
- Network frames connect from their left handle (sl-connect), like resources —
  use "sl" when the source is a network.
- Expose the React-only canvas on window.__ccDraftCanvas in the test build and
  read it from readDraftCanvas (localStorage persistence was removed), same as
  the changeset hook.
- A no-network resource is tracked (create-resource with a "No Network" issue)
  and its referencing policy is tracked too — the resource, not the policy, is
  the deploy blocker. Rewrite the "untracked resource" test to that model.
- network → policy on an empty network opens the "Select Destination" picker in
  its no-resources state; assert the modal, not the resource selector.
- Group context menu item is "View Details", not "Details".

Matrix is at 24 passing; remaining tests in the serial suite are still being
worked through.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 15:23:38 +02:00
Eduard GertandClaude Opus 4.8 29296ea17f Untrack accidentally committed playwright report zip; ignore the pattern
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 14:33:22 +02:00
Eduard GertandClaude Opus 4.8 d7542d7890 Align placeholder-peer policy test with the tracked-but-blocked model
A policy that references an uninstalled placeholder peer is listed as an
ordinary create-policy change; the peer's own install-peer change carries the
blocking issue that gates the deploy (DraftChangesetContext getChangeIssue).
The test asserted the opposite (policy withheld from the changeset) and only
"passed" before because readDraftChanges read the now-empty localStorage.
Assert the real model: create-policy + install-peer tracked, 3 changes total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 14:32:18 +02:00
Eduard GertandClaude Opus 4.8 7f4251c9cd Fix control-center connect/live e2e tests
- connectNodes: drive React Flow's connection as its own paced pointer drag
  (dwell on the target's full-area handle) instead of reusing the panel drag,
  so group/peer/resource connections actually open the create-policy modal.
- Expose the React-only changeset on window.__ccDraftChanges in the test build
  and read it from readDraftChanges (localStorage persistence was removed).
- Detect the policy modal by its heading ("Create New"/"Update Access Control
  Policy"), not the policy-name field — that field lives on the Name tab while
  the modal opens on the Policy tab. Reach the name field via its tab when a
  test needs to rename.
- live: a policy opened via Edit or left-click opens the modal directly; the
  "live mode" warning is deferred to Save. Assert that flow.

Fixes the reported connect/live failures; some later tests in these serial
suites (placeholder-peer changeset, peer→resource, …) are now exposed and
still need work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 14:20:00 +02:00
Eduard GertandClaude Opus 4.8 50e4df9b40 Fix control-center e2e tests to match current behavior
Align the control-center suites with the app as it stands and make the
components-panel drag helper reliable:

- Add data-testid to the components-panel category tabs and aria-pressed to
  toolbar toggle buttons so tests can select a template's category and read
  the panel's real open state (it only fades, so DOM visibility is unreliable).
- dragTemplateToCanvas: open the panel via the toggle's aria-pressed state,
  then switch to the tab holding the template (peer/group/resource/network).
- Drop the localStorage/reload-restore assertions — draft state is React-only
  (persistence was removed); the reload test now asserts the draft is
  discarded on reload.
- live policy Edit opens the modal directly and defers the "live mode" warning
  to Save; update the test accordingly.
- Fix stale/ambiguous deploy-flow locators (Review & Deploy heading; the
  create badge reads "Add").

draft.spec is green; connections/matrix reach the node-to-node connect drag
and the live edit-warning test still need work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 13:07:10 +02:00
Eduard GertandClaude Opus 4.8 ade7f0a6d9 Restructure control-center: group loose files into folders
Move the loose top-level component files into intent-named directories
(contexts/, header/, menus/, panels/), relocate ConnectionLine into edges/,
and split the draft/ grab-bag by pulling the Draft* modals into
draft/modals/. Only file moves plus the absolute-import-path updates they
require — no logic changes. CLAUDE.md's directory description is synced to
the new layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 11:07:44 +02:00
Eduard GertandClaude Opus 4.8 6acd5ddf00 Trim excessive comments across control-center PR
Remove comments that restate what the code or a well-named identifier
already says, drop process/past-tense and commented-out-code notes, delete
pure divider banners, and relocate trailing comments above their code. Keep
and tighten the non-obvious why-comments (ReactFlow perf traps, ordering
constraints, lazy-fetch and magic-number rationale).

Comment-only: no executable code, types, JSX, or imports changed. Verified
via JSX-transform transpile comparison, tsc --noEmit, and the unit suite
(253 tests passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 10:54:11 +02:00
Jack Carter af7745a03b chore: remove solutions engineer booking modal from onboarding (#745)
The onboarding survey opened a HubSpot booking modal for accounts with
50+ planned users. Removes the modal, its onboarding-call experiment
variants, the trigger in Onboarding.tsx, and the now-unused avatar.

Keeps src/cloud/cloud-hooks/useExperiment.ts, which this was the only
consumer of, for a future experiment.
2026-08-06 10:48:31 +02:00
Eduard GertandClaude Opus 4.8 4ac549ae14 Refactor legacy standalone test scripts into vitest
ip.test.ts and version.test.ts were hand-rolled runners (console.log +
process.exit) excluded from the vitest run and executed directly with
node/tsx. Convert them to describe/it.each/expect and drop the exclude so
they run with the rest of the unit suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 10:17:40 +02:00
Eduard GertandClaude Opus 4.8 da47262689 Remove debug return-false override in useIsFeatureLocked
Drop the temporary 'do not commit' short-circuit so feature-lock
resolution runs its real logic again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 10:05:56 +02:00
Eduard Gert c473245b6b Merge branch 'main' into feature/control-center-draft-mode
# Conflicts:
#	src/app/(dashboard)/control-center/page.tsx
#	src/modules/control-center/FlowSelector.tsx
2026-08-06 09:56:46 +02:00
Eduard GertandClaude Opus 4.8 c2c3b73aa8 Control-center: fix drilled positions, dive timing, deploy spinner
- Drill-in defers the camera dive one frame so the drilled view (header
  switch) paints before it starts, instead of competing in the same frame.
- Reveal waits for the swap to actually commit (afterSwapSettled) rather
  than a blind delay, so a heavy swap can't steal the tween's first frames.
- Switching networks while drilled no longer re-snapshots the drilled
  positions as the overview (frames collapsed on exit); snapshot is taken
  only when entering from the overview.
- Add Resource in a drilled network keeps its cursor placement — save
  skips the redundant re-assign that re-gridded it to center.
- computeFinalViewport slot lookup is O(1) instead of an O(nodes) filter
  per child.
- Deploy button spinner stays centered: centering moved to a wrapper so
  animate-spin's transform no longer clobbers the translate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 09:41:01 +02:00
Eduard Gert 73eba6fd0e Revert "Control-center: restore snappy drilled empty-network card entrance"
This reverts commit eb178c8b92.
2026-08-05 16:55:40 +02:00
Eduard GertandClaude Opus 4.8 ee645bce09 Control-center: drilled 'New Resource' lands under the cursor
Right-clicking the canvas in the drilled network view and adding a
resource dropped the card at the frame's next grid slot (centered),
ignoring the click point — unlike the non-drilled 'New Resource', which
creates at the cursor via createStandaloneAt.

Thread the right-click flow position through the resource editor
(createAt) into addResourceToFrame, convert it to the frame-relative
child position, and stamp drilledFreePos so useNetworkFrameLayout leaves
the card where it dropped instead of snapping it into the grid — the
same treatment a card dragged in the drilled view already gets. Exiting
the drill-down clears the marker and re-grids, as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 16:52:36 +02:00
Eduard GertandClaude Opus 4.8 eb178c8b92 Control-center: restore snappy drilled empty-network card entrance
The softer 700ms/zoom-in-[.97] entrance read as a position jump when
drilling into a network. Revert to the canvas reveal's fade/zoom timing
(450ms, zoom-in-[.7]) so the empty-state card settles in with the scene
instead of lagging behind it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 16:46:24 +02:00
Eduard GertandClaude Opus 4.8 5d8e14f5a9 Control-center: fix dropped resource landing at wrong position in a frame
Dragging a standalone draft resource into a network frame left it at a
temporary slot (above the header) until the frame was moved. The reparent
in assignResourceToNetwork runs via reactFlow.setNodes, whose updater reads
the xyflow store — which at drag-stop still holds dragging:true — so
spreading ...n baked a stale drag flag into the child. useNetworkFrameLayout
bails while any node is dragging, so the reconciler never snapped the child
into its grid slot until a frame move cleared the flag.

Clear dragging on the reparented node so our own write can't trip the guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 16:26:04 +02:00
Eduard GertandClaude Opus 4.8 838d3dcaa2 Control-center: compact routing-peers bar on the network frame node
The floating "No Routing Peer" bar above a network frame rendered at the
header's 40px height, reading as too tall next to the node's other floating
overlays. Add a `compact` prop (h-[34px], matching the node's floating
"Install" button) and pass it only from the frame node — the drilled-network
header and live single-network header keep the 40px height.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 16:13:16 +02:00
Eduard GertandClaude Opus 4.8 bfb02b091b Control-center: trim verbose comments
Reduce the comments added across recent control-center work to only
non-obvious whys; drop the ones that restated what the code does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 15:45:53 +02:00
Eduard GertandClaude Opus 4.8 099a0314d5 Control-center: folded group shows real resource count; softer empty entrance
The folded group row in live frames read the policy-embedded group snapshot
(no counts -> "No Resources") while draft used fresh /groups data; live now
resolves the fresh group too, so both agree. Also soften the drilled
empty-network card's entrance (slower fade, subtler zoom) so it settles in
instead of flashing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 15:45:41 +02:00
Eduard GertandClaude Opus 4.8 3eb03649fe Control-center: speed up the live<->draft switch
Order draft nodes to match the live array order so React patches them in
place instead of moving their DOM subtrees on the switch, and defer the
switch past the chooser popover so its scroll-lock teardown no longer
thrashes the rebuild. Also index the build's per-item lookups (Map/Set)
to drop its O(n^2) scans. Profiled: switch INP ~1288ms -> ~104ms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 15:45:32 +02:00
Eduard GertandClaude Opus 4.8 ff01f23337 Control-center: mark folded resources as on-canvas in the components panel
A grouped resource is folded into its group's row inside a network frame,
so it has no resource-<id> node of its own — the panel's on-canvas check
missed it, leaving it draggable. Re-dropping it spawned a duplicate
standalone node and drew a stray group->resource line from the folded row.
Treat a resource as on-canvas when it belongs to a group folded into a
frame (resourceGroupNode) on the canvas, so it greys out and can't be
re-added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 13:05:59 +02:00
Eduard GertandClaude Opus 4.8 7c47537656 Control-center: create a group from selected resources in a drilled network
Rubber-band select resource cards inside a drilled network and the
selection toolbar's Create Group now folds them into a new resource
group that lives in that network's frame. Drilled frame children are
made selectable (parent rows stay non-selectable to avoid full-width
graze), the toolbar positions from absolute bounds so it sits over frame
children, and the new group is placed at the frame-relative center of the
selection (drilledFreePos) instead of the bottom grid slot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 13:05:50 +02:00
Eduard GertandClaude Opus 4.8 fe91a61b76 Control-center: drop no-op d3 simulation from hierarchical layout
applyD3HierarchicalLayout placed every node via centerNodesVertically,
then ran a 100-tick d3 simulation whose forces were all no-ops
(zero-strength charge, zero-radius collision, a position force targeting
each node's own coordinate). It moved nothing and only blocked the main
thread on every view build — most visibly freezing the canvas on the
live<->draft switch. Read the placed positions directly and look up edge
endpoints via a Map instead of an O(edges x nodes) find. Layout output
is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 12:28:02 +02:00
Eduard GertandClaude Opus 4.8 ac4a680742 Control-center: fold grouped resources into group row in live frames
Live network frames now represent a grouped resource by its group row
instead of also listing it as its own row, matching the draft build so
live and draft frames show the same rows in the same order. Align the
draft frame's resource input order with live, adopt the folded group
row's live twin size to stop a flash on the mode switch, and offer
Rename on folded existing resource groups (not just new ones).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 12:19:32 +02:00
Eduard GertandClaude Opus 4.8 b5f54cc790 Control-center: live connection preview when editing group membership
Toggling resources in the group panel now previews the drilled
group↔resource edges (and subtitle counts) on the canvas immediately,
reverting on cancel and persisting on save. Drive the preview
imperatively from the toggle handlers instead of a selection effect so
opening a group no longer flashes a stale count, and drop the live-save
view rebuild so saving no longer refits/auto-arranges the canvas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 11:46:09 +02:00
Eduard GertandClaude Opus 4.8 c77d572c5a Control-center: drilled network view layout & edge fixes
- The group→resource "simple" edge leaves from the group's right handle (sr)
  instead of defaulting to the left one.
- Drilled view no longer auto-arranges on drop: each frame child is pinned
  after its first (fixed-pitch) placement, so moving or absorbing a dropped
  resource doesn't re-grid the rest. The parent view drops the pin and
  re-grids as before.
- Entering draft no longer duplicates resources already shown in a group: a
  resource that's a member of a group on the canvas and has no direct policy
  connection is left off as a standalone child (the group covers it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 11:04:12 +02:00
Eduard GertandClaude Opus 4.8 6d4e549090 Control-center: live policy edit opens directly, confirm on Save Changes
The policy modal now opens immediately in live mode; the "you are in live mode"
confirmation is deferred to when the user clicks Save Changes, via a new
onBeforeSave hook on AccessControlModal (awaited before it submits). Draft is
unaffected (useSave=false). The confirm-before-open was removed from the policy
left-click and the context-menu Edit in the prior commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 10:47:49 +02:00
Eduard GertandClaude Opus 4.8 5e5f737430 Control-center: hover consistency + stable drilled-view layout
- Icon boxes light up on hover consistently: resource rows in live (was
  draft-only), group nodes, and peer cards (DeviceCard's unnamed group-hover
  now fires via the peer wrapper).
- The "+N more" cell no longer lights up when the whole frame is hovered (its
  own `group/more` name instead of the frame's `group/node`).
- Restore hover on resource/destination groups in the live drilled view (drop
  the stray hoverable:false); a live resource group drills from the overview
  (onNodeClick), a no-op once already drilled.
- Drilled view no longer auto-arranges: the frame-layout reconciler keeps a
  stable child order while drilled instead of re-sorting by position every
  change, so moving/absorbing a node doesn't shift the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 10:47:37 +02:00
Eduard GertandClaude Opus 4.8 f8b5e12fd5 Control-center: network-frame "View Details" menu + resource-group interactions
- Add a "View Details" item (eye icon, top) to the network-frame right-click
  menu that drills into the network — live and draft. (The policy-frame Edit
  now opens the editor directly; its confirmation moves to save-time.)
- Drag a resource onto a group in the drilled view: it falls through to the
  group-drop instead of just repositioning, resourceGroupNode is a valid drop
  target, the dropped resource is absorbed (card leaves the canvas), and the
  target group rings while a resource hovers it.
- Resource-group side panel now resolves its members (the panel matches
  resourceGroupNode) and opens on the Resources tab for resource-leaning groups.
- New resource groups uniquify their name against live groups + pending creates
  (fixes "group already exists" on deploy).
- A framed resource group behaves like a resource row: pointer + hover, drills
  in live, opens the panel in draft.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 10:47:21 +02:00
Eduard GertandClaude Opus 4.8 fd81190422 Control-center: poll for placeholder registration after setup-key creation
- After a setup key is generated for a Server/Agent placeholder, poll /peers
  (every 5s while any placeholder is waiting) so the upgrade watcher swaps the
  placeholder for the real peer as soon as it registers. Self-terminates once
  all waiting placeholders have upgraded.
- Badge reflects the waiting state: the canvas placeholder shows a spinning
  Loader2 + "Waiting" (instead of "Install"), and the Review & Deploy install
  IssueBadge shows a spinner + "Waiting" with its chevron — both still open the
  install modal, and it still blocks deploy until the peer registers.

Wiring: InstallPeerChange.setupKeyId + markInstallPeerWaiting, set from
DraftInstallPeerModal.onSetupKeyGenerated; getChangeIssue returns a waiting
issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 17:48:43 +02:00
Eduard GertandClaude Opus 4.8 6a36c03e19 Control-center: clicking a group in the drilled network view opens its panel
Source group nodes in the single-network view carried an onClick that navigated
to the whole group view, which was jarring from a drilled network. Drop it so
the click falls through to onNodeClick and opens the group's side panel, like
every other view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 17:33:44 +02:00
Eduard GertandClaude Opus 4.8 c98c127e6f Control-center: Review & Deploy progress + group membership fixes
Review & Deploy:
- Per-change deploy progress: the change being applied swaps its ⋮ for a white
  spinner and the row pulses; done rows show a green check; changes stay listed
  (skipped on retry) instead of being removed as they land.
- Deploy button shows a centered spinner at fixed width; button/Cancel disabled
  and the modal can't be dismissed mid-deploy.
- On success: switch to live, close, then reset the changeset after close (no
  empty-modal flash); a "Deploy complete" toast; errors keep the app's usual
  red toast. The rows render against a frozen live snapshot so revalidation
  can't flip a row to "Modify" mid-deploy.

Group/resource membership:
- The group POST/PUT sends resources as {id, type} objects (bare id strings are
  rejected — "could not parse json"), via a resourceType resolver, in both the
  deploy and the reviewed cURL.
- Live group/peer panel saves revalidate /policies (and /networks/resources) so
  Save un-dirties and other views / the draft don't show stale counts; the
  draft build reads group counts from fresh SWR /groups.
- Panels: Save/Assign show a fixed-width spinner while saving and close on
  success.
- getGroupCountLabel leads with the larger side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 17:21:01 +02:00
Eduard GertandClaude Opus 4.8 ccd8ee5344 Control-center: drop the white drop-eligible border on group drag
Dragging a peer/resource gave every eligible group a white border
(dropEligible) at drag start, which read as noise. Remove it (styling, the
onNodeDragStart write, and the prop); only the group under the pointer keeps
its dropTarget ring. Drag-stop now clears any dropTarget directly, since it no
longer relies on dropEligible to clear the hovered group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 16:20:41 +02:00
Eduard GertandClaude Opus 4.8 e28f1afd23 Control-center: fix right-click lag; offer Focus on draft network frames
- Right-click lag: the context-menu halo lived in CanvasUIContext, so every
  node re-rendered on each right-click; and opening the menu flipped
  nodesDraggable/connectable/selectable, which re-renders every node wrapper.
  Move the halo to a per-node external store (useIsContextMenuTarget) so only
  the target re-renders, and stop gating node interactivity on the menu (it
  dismisses on any click/scroll anyway; pan/zoom stay locked via canInteract).
- Add the Focus item to the draft network-frame menu (the live frame menu
  already had it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 16:08:26 +02:00
Eduard GertandClaude Opus 4.8 2187e5dbdf Control-center: rename Highlight Connections to Focus; drop em dashes from UI strings
- Rename the "Highlight Connections" tool to "Focus" (Focus icon) across the
  node menu, header pill, exit label and the e2e test; the F shortcut now
  name-aligns.
- Remove em dashes from user-facing strings: the live-mode confirmations
  (policy/resource/group/peer save, edit and toggle), the discard confirm, the
  deploy error messages, and the no-network issue message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 14:30:24 +02:00
Eduard GertandClaude Opus 4.8 f5b05c4bc0 Control-center: fix live resource-edit crash; drop em dashes from PR text
- Live resource edit in a drilled network crashed with "assignedPolicies is
  not a function": the edit modal rendered NetworkResourceModal without the
  NetworkProvider / NetworkAccessControlProvider that supplies it. Wrap it in
  those providers, like the draft resource modal and the networks page.
- Rewrite the em-dash comments (and one confirm string) added in this PR into
  simpler sentences.

Test: new e2e "edit a live resource inside a drilled network".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 14:11:24 +02:00
Eduard GertandClaude Opus 4.8 fd2093e491 Control-center: fix live-mode canvas refresh, panel save/order, deploy accordion
- Live add-resource: revalidate SWR + reset layoutInitialized so the new
  resource shows on the canvas without drilling in/out.
- Policy left-click in live mode now confirms first (like the menu Edit).
- Side panels: pin rows to a full ordered-id snapshot frozen per open
  (pinByOrder) so a save's follow-up mutate can't reshuffle them.
- Group/peer panel live save: send resources as {id,type} objects (the API
  rejected id strings with "could not parse json"); drive the toast off the
  request promise so failures render red instead of a green success.
- Side panels reposition on window resize.
- Server/agent placeholders drop the hostname suggestion (they match by their
  hidden bound group now); only user devices keep it.
- Review & Deploy opens the genuine first accordion (server/agent install
  steps included).

Tests: 4 pinByOrder unit tests; new e2e in control-center-live and
control-center-draft-install.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 13:49:37 +02:00
Eduard GertandClaude Opus 4.8 59e39113a0 Control-center: cascade-aware changeset removal
Removing a change from Review & Deploy now reverts the draft as if the change
had never happened, cascading to the canvas AND to dependent changes (instead
of the old flat removeChange that left both out of sync).

- utils/change-cascade.ts (pure, unit-tested): reduceRemoveChange computes the
  resulting changeset per type — create-* drop the change + dependents
  (network detaches its resources to "No Network", group-by-name refs stripped,
  a one-sided policy left behind is dropped); update-*/delete-* drop only the
  target (reverting is a canvas restore). previewRemoveChange builds the
  "warn with specifics" list.
- hooks/useRemoveChange.ts: the dispatcher — applies the changeset reduction and
  the matching canvas mutation (remove nodes/edges, detach a network's
  children, revert node data to live, or restore a deleted node via
  drawPolicyOnCanvas / the new draft-node-factory). install-peer delegates to
  removeNodeWithEdges (sweeps the setup-key / bound-group artifacts).
- utils/draft-node-factory.ts: pure builders (extracted from useDraft's
  carryNetworkFrame) that rebuild a network frame / resource / group from live
  data when a delete change is reverted.
- ReviewDeployModal / ChangeAccordionItem: route removal through the dispatcher
  and show the cascade specifics in the confirm dialog; add cc-change-* testids.

Tests: change-cascade.test.ts (10 unit) + control-center-draft-remove.spec.ts
(5 e2e: create-network detach, create-group→policy drop, delete-network /
delete-policy restore, update-policy revert).

Test infra: suppress the cloud billing modal in control-center e2e via the
netbird-test-edition override (it isn't billing-gated); harden getApiContext to
wait for an authed /api response (avoids capturing a tokenless early response
right after a restart).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 10:56:54 +02:00
Eduard GertandClaude Opus 4.8 e4b46a8878 Control-center: simplify comments across the PR
Comment-only cleanup over the changed files: keep comments only for
non-obvious "why", tighten verbose ones, and drop comments that restate
what the code already does. Also removes dangling references to the
deleted specs/*.md and a couple of stale comments that contradicted the
code. No code, behavior, or types changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 09:41:25 +02:00
Eduard GertandClaude Opus 4.8 17c48b054d Control-center: e2e tests for live and draft mode
Add end-to-end coverage for the control-center draft-mode PR (live + draft):

- Live: mode/view chrome, flow-view switching, network render/edit/delete and
  drill-down, resource menu, policy Edit/Enable/Disable/Delete, group View
  Details/Rename, focus mode (menu + "F" key).
- Draft: Alt+digit canvas shortcuts, New Network frame + context menu, Auto
  Arrange, network Edit, resource enable/disable/rename, Add Routing Peer
  modal, drill-down + add resource, multi-entity deploy (verified in account),
  and the full placeholder install -> docker-peer -> upgrade flow.
- Peer view exercised with a real docker peer registered via a setup key.
- Helpers: docker-peer registration/run, API seeders (network/policy/resource/
  setup-key/peer), flow-view + overlay helpers.
- Add data-testids to support the above (flow tabs, toolbar arrange, network
  drill-down back/add-resource, peer install, setup-key generate).
- Fix the login proxy-wait test's soft-fail (bound the cluster poll below the
  test timeout) so a slow/absent cluster no longer blocks every dependent spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 09:16:46 +02:00
Maycon Santos 19d82cc2ab Handle agent-network settings defaults response (#739)
* Handle agent-network settings defaults response

The management API now answers the settings GET with a defaults object
before the account is bootstrapped (empty cluster/subdomain/endpoint)
instead of a JSON null body. Normalize that shape to the existing null
settings signal so every consumer keeps a single not-bootstrapped check,
while still tolerating the null body and 404 from older backends.

* Pin the settings wire-shape normalization in a Playwright spec

The providers page must render the connect-first empty state for every
unbootstrapped settings shape the management API has answered with over
time (defaults object with an empty endpoint, JSON null body, 404) and
the endpoint badge once bootstrapped. The settings route is mocked per
test so the spec is independent of the backend build and of account
state left behind by other suites.

* Address review: no stale settings on 404, close context on setup failure

useFetchApi keeps the previous SWR data alongside the error, so a later
404 could expose stale settings through the memo; it now yields null
when the error is a 404 while still riding out transient failures on
the kept data. The wire-shapes spec closes its browser context when a
setup step throws before the close callback is handed to the test.
2026-08-04 01:59:34 +02:00
Eduard GertandClaude Opus 4.8 0d031732aa SelectDropdown: gate the deferred onChange behind an opt-in prop
The onChange defer (fire after the ~180ms close animation) shipped globally,
delaying every dropdown in the app. Make it opt-in via `deferChange` (default
off = synchronous, the pre-existing behavior) and enable it only on the
control-center dropdowns whose onChange rebuilds the canvas (the entity
selectors and the header/title network selectors). The two lightweight draft
modal pickers stay synchronous.

Adds a component test pinning both paths (sync by default, deferred when set).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 17:37:10 +02:00
Eduard GertandClaude Opus 4.8 db9f90ec91 Control-center: drop dangling references to the removed specs/*.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 17:31:00 +02:00
Eduard GertandClaude Opus 4.8 ad896a74bb Control-center review fixes, regression tests, and unit-test CI
Addresses the branch review findings (all but the intentionally-skipped
useIsFeatureLocked debug return) plus follow-ups from this pass.

Correctness fixes:
- Live routing-peer EDIT now PUTs instead of recording a dead draft change;
  the draft frame path still records update-router (DraftRoutingPeerModal).
- Deploy resolvers persist client→real id maps across deploy() calls (+ live
  network name fallback) so a partial-deploy retry no longer strands
  network/resource dependents (useDeployChangeset).
- Review & Deploy discard of a draft network cascades (untrackNetwork).
- Group-absorbed placeholder peers now store/clean their setup-key + bound
  group so no artifacts are orphaned (DraftInstallPeerModal, useDraftPeerUpgrade,
  useDiscardDraft, useDraftGroupActions).
- trackUpdateResource drops a field-for-field revert; renameGroupInPolicies
  follows into update-resource/update-router; trackDeleteNetwork cascade also
  drops update-router/update-network.
- handleEntityChange no longer runs the d3 layout / side effects inside the
  setNodes updater (pure updater) while preserving view-switch fitView.
- Live network delete removes the frame only after the DELETE succeeds.
- Undo/redo flush the pending debounced capture so an undo within the debounce
  window can't overshoot or lose the in-flight edit (DraftHistoryContext).
- Live membership panels surface failures and re-sync (DestinationGroupPanel,
  PeerGroupsPanel).
- isCompleteDraftResource checks the raw name; sortBy no-op direction arg
  removed (behavior preserved); PeerNode reads placeholderIp from context
  instead of a per-node account fetch; defensive create-router dedup.

Regression tests (run green on both main and this branch):
- New component tests for the app-wide shared components most affected:
  SelectDropdown (deferred onChange contract), SegmentedTabs, Accordion.

CI:
- Add .github/workflows/unit-tests.yml running `npm run test:unit` on push/PR
  (183 tests, no backend needed).

Docs:
- Remove the control-center specs/*.md design docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 17:29:20 +02:00
Eduard GertandClaude Opus 4.8 06c65f7ece Control-center draft: list both-sides policies referencing uninstalled peers
A policy connected to a not-yet-installed placeholder peer (draft-… id) was
silently withheld from Review & Deploy: isDeployablePolicy gated tracking, and
a draft- peer reference made it false, so the policy was drawn on the canvas
but never entered the changeset.

Split the predicate: isTrackablePolicy (both sides set, draft resources tracked
— tolerates an uninstalled peer) now gates entry into the changeset, while
isDeployablePolicy stays the stricter deploy-time gate. The policy is listed as
an ordinary change; the deploy is still blocked by that peer's own install-peer
issue, and installing it re-records the policy with the real id via
usePlaceholderUpgrade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 15:12:29 +02:00
Eduard GertandClaude Opus 4.8 8ce4cd2b89 Control-center: drop live "Add Resource Group"; fix no-network resource modal copy
Remove the "Add Resource Group" action from the live network-frame context menu
(and its create-group name dialog, handler, state, and now-unused imports). The
live frame menu is now Add Resource / Add Routing Peer / Delete. Draft mode's
Add Resource Group is unchanged.

Also fix the resource modal header: a draft standalone resource has no network
yet, so it rendered 'Add new resource to ""'. Show 'Add a new resource' when
there's no network name; keep 'Add new resource to "<name>"' otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 14:35:01 +02:00
Eduard GertandClaude Opus 4.8 7e2cba309a Control-center: live-mode immediate CRUD for networks, policies, peers
Live-mode control-center actions now hit the API immediately, since the draft
changeset can only be deployed from draft mode (a change recorded in live never
deploys). This reworks the previously changeset-based live network delete and
fixes the same latent dead-end in live network edit.

Networks (single-network top bar ⋮ + button):
- Edit  -> immediate PUT /networks/{id}
- Delete -> immediate DELETE /networks/{id} + refresh, then deselect
- Add Resource -> real POST (DraftResourceEditorModal falls back to the API
  network list, as the live single-network view has no frame node)

Network frame right-click (networks overview) — networkNode added to the live
context-menu allowlist, new live frame branch:
- Add Resource / Add Routing Peer -> immediate POST
- Add Resource Group -> Create Group name dialog -> POST /groups (empty group)
- Delete -> immediate DELETE /networks/{id}

Policy right-click: Delete -> usePolicies().deletePolicy (DELETE /policies/{id}).
Peer right-click (was View Details + Rename only): Delete -> DELETE /peers/{id}.

useDeleteNetwork is now mode-aware (draft: changeset; live: immediate API).
Draft mode behavior is unchanged. All live actions confirm first and mirror the
networks/peers/access-control page copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 14:30:03 +02:00
Eduard GertandClaude Opus 4.8 94fac0e509 Control-center: ⋮ actions menu + draft Add Resource in single-network top bar
Replace the pencil edit button in the drilled single-network top bar with a
three-dots actions menu (⋮). It shows Edit in both live and draft; Delete
(existing networks) / Remove (draft-created) is draft-only, since deleting a
network is a draft/deploy flow with no live equivalent. Deleting the network
you're drilled into exits the drill-down.

Add an "Add Resource" button after the routing-peers bar in the draft
drill-down header (opens the resource editor targeting that network).

Extract the network-delete logic that was inline in NodeContextMenu into a
shared useDeleteNetwork hook so the context menu and the header confirm and
behave identically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 14:02:31 +02:00
Eduard GertandClaude Opus 4.8 8dbd677367 Control-center routing-peers dropdown: fix extra bottom padding
The Virtuoso viewport was sized from estimatedItemHeight=34 while the real
row is ~32px, so the surplus rendered as a dead strip below the last row —
reading as more padding at the bottom than the top. Match the estimate to the
real row height so top and bottom gaps are equal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 13:33:58 +02:00
Eduard GertandClaude Opus 4.8 44b32af17c Draft changeset: id placeholders read name_type_id, e.g. {server_group_id}
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 13:27:52 +02:00
Eduard GertandClaude Opus 4.8 b5ce21eef2 Draft changeset: setup-key auto_group shows {group_x_id} placeholder, not name
The server/agent install-peer preview built auto_groups directly from the
group name ("<name> (Draft)"), bypassing the resolvers. The hidden bound
group's real id doesn't exist until the key is generated, so render it as an
id placeholder like the other not-yet-created ids.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 13:26:01 +02:00
Eduard GertandClaude Opus 4.8 810a0619f6 Draft changeset: single-brace lowercase id placeholders, e.g. {group_x_id}
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 12:36:58 +02:00
Eduard GertandClaude Opus 4.8 9d5ba3f201 Draft changeset: show {{..._ID}} placeholders for not-yet-created ids
The Review & Deploy code view leaked draft names / client-ids where a real
API id gets filled in on deploy (e.g. a policy whose source is a draft group
showed the group NAME). previewResolvers now renders any id that doesn't
exist yet as an unmistakable {{KIND_LABEL_ID}} token:

- draft group (no id / draft-group name) -> {{GROUP_<NAME>_ID}}
- draft resource referenced by a policy -> {{RESOURCE_<NAME>_ID}} (name from
  the create-resource change, threaded via LiveData.draftChanges)
- draft network -> {{NETWORK_<NAME>_ID}}; the path's old {new} -> {{NETWORK_ID}}

Real ids and live-group names still resolve exactly as before. Adds the
idPlaceholder helper and 3 preview tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 12:35:23 +02:00
Eduard GertandClaude Opus 4.8 00f8b3c6af Draft: add update-network and update-router change types
Editing an EXISTING network or router in draft mode previously bypassed the
changeset and PUT to the live API immediately. Both now record a deployable
draft change instead.

Changeset (DraftChangesetContext):
- UpdateNetworkChange / UpdateRouterChange types + union, getChangeKind
  ("update"), getChangeApiCall (PUT /networks/{id}, PUT .../routers/{id}),
  labels, and CHANGE_DEPLOY_ORDER slots after their create siblings.
- trackUpdateNetwork coalesces per network id and drops on revert to the live
  name+description; trackUpdateRouter coalesces per router id.

Request bodies + deploy:
- networkUpdateBody / routerUpdateBody (same shapes as create), wired into
  buildChangeRequest, the code-view before/after (network has a live before;
  router is after-only — routers aren't in the global live data), and the
  deploy executor.

UI:
- DraftNetworkEditModal: existing network runs the live modal in
  useSave={false} mode -> trackUpdateNetwork; the frame label updates live.
- DraftRoutingPeerModal: API router -> updateRouterFromSelection (mirrors
  addRouterFromSelection, incl. draft-group creation) -> trackUpdateRouter.
- useFrameRouterRows overlays a pending update-router onto its API row so the
  edit shows on the canvas before deploy.

change-presentation exhaustive switches updated; 3 changeset unit tests added;
module CLAUDE.md corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 12:14:49 +02:00
Eduard GertandClaude Opus 4.8 e7ee3ebc51 Control-center routing-peers dropdown: fix reopen, dismiss, styling
- Adding a routing peer no longer springs the dropdown open afterwards: the
  empty-state "No Routing Peer" click can't latch the popover open behind the
  suppressed (zero-row) list (preventDefault on the trigger).
- Clicking the ReactFlow canvas (or anywhere outside) now dismisses the
  dropdown — a capture-phase pointerdown listener, since the canvas pane
  stops propagation before Radix's bubbling outside-dismiss can see it.
- No first-row pre-highlight on open: VirtualScrollAreaList gains an opt-in
  autoSelectFirst prop (default keeps combobox behavior); the read-only
  routing-peers list passes false.
- Dropped the search box; trigger shows a caret-down (ChevronDown) instead of
  the select up/down arrows; rows match the header network selector's sizing
  (text-xs font-medium, py-1 px-2, nb-gray-910 hover) with balanced padding.
  Rows keep the hover-pencil / click-to-edit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 11:02:35 +02:00
Misha Bragin b66cc4ccb7 Cache pricing + custom model entry (#736) 2026-08-01 15:56:24 +02:00
Eduard GertandClaude Opus 4.8 770ece3144 Draft drilled: fix adding a resource into an empty drilled network
Two issues when a network is drilled into while empty in draft mode:

- The drilled-in camera was framed from an empty keep-set, so its bounds
  stayed Infinity and the viewport resolved to ±Infinity: a blank canvas
  stuck at an invalid transform that couldn't be panned, with resources
  added into the frame landing off to the side. Fall back to framing the
  camera on the hidden frame's own rect when nothing is kept.

- A resource created into a drilled (hidden) frame had no measured size, so
  React Flow kept it at visibility:hidden — it rendered but couldn't be
  selected or dragged ("fixed, not really on canvas"). Seed initialWidth/
  Height on creation so it's measured on mount and interactive immediately.

Verified in-browser: drilling into an empty network gives a finite,
pannable camera; the added resource shows centered, is draggable, and the
canvas pans.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 18:29:56 +02:00
Eduard GertandClaude Opus 4.8 3cfbc07255 Draft control-center: ring framed resource group on panel open; promote to group card when drilled
- Open group panel rings the framed resource group's icon box sky-500,
  matching GroupNode's panel-active highlight and the framed-resource halo.
- Drilling into the single-network view (parent frame hidden) promotes the
  framed resource group row to a full GroupNode card; it reverts to the flat
  row back in the networks view — same drilled-child signal ResourceNode uses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:41:35 +02:00
Eduard GertandClaude Opus 4.8 3f6db3c89c Draft changeset: shrink server icon to match agent/group
The ServerIcon glyph reads visually heavier than the bot/monitor/folder
icons at the same pixel size in the review & deploy list, so drop it 2px.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:40:48 +02:00
Eduard GertandClaude Opus 4.8 305b0d065a Draft control-center: click framed resource → edit modal, resource group → panel
In draft mode, clicking any resource inside a network frame now opens the
draft resource editor (was limited to new resource-new-… rows); clicking a
framed resource group opens the destination group panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:33:53 +02:00
Eduard GertandClaude Opus 4.8 a83903bd71 Draft control-center: track create-group when adding a resource group
The network frame's "Add Resource Group" action (addResourceGroupToFrame)
inserted a new draft group node but never recorded a create-group change,
unlike every other new-group path. The group never appeared in Review &
Deploy and no POST /groups ran on deploy; a later resource reference to it
by name would resolve to a bogus id. Call trackCreateGroup after inserting
the node so the create is tracked and coalesces with later rename/member
changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:33:08 +02:00
Eduard GertandClaude Opus 4.8 79b12fc51e Draft control-center: keep drill-down header outside networks view
Commit 0dc287c6 hid the all-networks selector when a draft was entered
from peers/users/groups by returning null for the whole
DraftDrillDownHeader when currentView !== NETWORKS. But that component
also renders the drill-down back button, network switcher, edit button
and routing-peers bar, so drilling into a network in such a draft hid
the entire header.

Gate the null return on the OVERVIEW state (!drilled) only. The
standalone all-networks selector stays hidden outside the networks view,
but once the user drills into a network the view-agnostic drill-down
header renders from any entry view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:27:31 +02:00
Eduard GertandClaude Opus 4.8 705fbc8c7c Draft drilled: free-drag resources, hide network suffix in live drill
Issue 1: the live single-network view showed "name - network" on resource
cards because they carry a draftNetwork ref (for the card look) but had no
signal to suppress the redundant inline suffix the way the draft drill-down
does. Carry an explicit `drilled` flag and hide the suffix on it.

Issue 2: draft drilled resources snapped back to their frame grid slot on
drop — useNetworkFrameLayout reconciled their positions every tick, while
live drilled resources are top-level nodes nothing repositions. Mark a
dragged drilled child free-positioned so the reconciler leaves it alone,
and clear the marker once it returns to the parent grid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 17:24:54 +02:00
Eduard GertandClaude Opus 4.8 548de5fce7 Control-center live empty states + in-canvas create flows
Show the drilled single-network "Create Resources" empty state and the
"Create New Network" empty state in Live mode, and wire their buttons to
the real networks-page modals (network → resource → routing peer) mounted
in place via NetworkProvider, so creating from the canvas no longer
navigates away.

- ControlCenterEmptyStates: add the drilled-empty-network state gated on
  layoutInitialized + the drill transition finishing (useCanvasTransitionActive),
  and fix the overlays being buried under the ReactFlow canvas by giving them
  z-10 (matching the header/toolbar overlays). Drop the card chrome via the
  new GetStartedTest cardClassName so the state floats over the canvas.
- GetStartedTest: optional cardClassName to override the Card background/border
  (default unchanged for the ~30 table call sites).
- canvas-transition: expose FADE_IN/REVEAL/GROW_IN_FROM and a reactive
  useCanvasTransitionActive store; settle an empty drilled view at
  EMPTY_STATE_ZOOM instead of the dive-in zoom.
- NetworkRoutingPeerCount / DraftRoutingPeerModal / DraftNetworkEditModal:
  make the network + routing-peer modals work from the live drilled view
  (no frame node) — Add/status click POSTs a new router, edits PUT, and
  results revalidate the relevant SWR keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 17:14:22 +02:00
Eduard GertandClaude Opus 4.8 0dc287c62f Draft control-center: hide all-networks selector unless draft entered from networks view
The draft drill-down header rendered the network selector (incl. "All
Networks") based solely on frame-node presence, so adding a network in a
peers/users/groups draft made it reappear. Gate it on currentView ===
NETWORKS, which stays frozen on the entry view through a draft session
(the live FlowSelector is hidden and no draft action calls setCurrentView).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:08:07 +02:00
Eduard GertandClaude Opus 4.8 7991172dfa Draft control-center: drag drilled resource standalone, hide No Network bar
When drilled into a network, its resources are still ReactFlow children of
the hidden frame. Dragging one triggered the frame-drag path (moving the
whole frame and every sibling), and the resource still rendered the floating
"No Network" bar even though it lives inside the drilled network.

Skip the frame-drag setup when the parent frame is the drilled network so
ReactFlow moves only that card, and gate the "No Network" bar on !hideNetwork
so drilled children never claim to be networkless.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:04:58 +02:00
Eduard GertandClaude Opus 4.8 7cb3700c41 Draft control-center: don't zoom/fit when adding a resource while drilled
Adding a resource inside a drilled network flung the camera to a strange
position. saveDraftResource calls highlightNetworkNode, which fitViews to
the network frame to draw the eye to it. While drilled the frame is hidden
and the drilled world already fills the view, so fitting to the hidden frame
threw the viewport off. Skip the fit + pulse while drilled, same as adding a
resource in normal (non-drilled) draft mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 17:02:36 +02:00
Eduard GertandClaude Opus 4.8 cb7d7d6152 Revert drill-down resource handling to pre-reparent behavior
Restore useNetworkDrillDown, useDraftNetworkActions, useNetworkFrameLayout,
and ResourceNode to their state before c5406d75. Drilled resources render as
flat frame-child rows again (useNetworkFrameLayout's drilled column branch),
dropping the reparent-based handling and the fixes layered on it (camera-jump
gate on add, add-during-drill positioning, empty-network empty state,
hide-inline-network-when-drilled) — they were causing strange behavior.

Kept: the "Draft your changes" empty-state removal, drill context-menu items,
drilled components-panel scoping, delete-network, and the Review & Deploy code
view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 15:24:58 +02:00
Eduard GertandClaude Opus 4.8 c7f3675722 Draft control-center: Review & Deploy code view — always diff, drop visual mode
- delete-network now shows a real diff: buildBeforeRequest reconstructs the
  network (name, description, resources as {name,address}, routers) so the
  code view renders it as an all-minus diff like the other deletes, instead
  of "No request body."
- Remove the "No request body." and "Not an API request…" fallbacks: every
  change has a request now (install-peer sends its POST /setup-keys), so
  buildChangeRequest is non-null; the code view always renders a diff, falling
  back to the request line only if a change genuinely has no body.
- Delete Visual mode entirely (unused — the modal hard-coded Code): remove
  ChangeVisualView and changeset-fields, the view prop, and switch the live
  type from FieldLiveData to LiveData (drops the now-unused peers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 15:16:29 +02:00
Eduard GertandClaude Opus 4.8 8154dd05e3 Draft control-center: delete a whole network in draft
Add a delete-network changeset type end to end:
- DraftChangesetContext: DeleteNetworkChange + trackDeleteNetwork (drops any
  pending resource/router changes for that network, since the API delete
  cascades them), plus kind/apiCall/label/deploy-order wiring.
- useDeployChangeset: DELETE /networks/{id} at deploy.
- changeset-request / change-presentation / changeset-fields: Review &
  Deploy preview (bodiless DELETE), icon, title, type label, field rows.
- NodeContextMenu: existing network frames now offer Delete (confirm →
  trackDeleteNetwork → canvas removal); draft networks keep Remove.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 14:52:11 +02:00
Eduard GertandClaude Opus 4.8 626ba20fdc Draft control-center: scope drilled components panel to the network
While drilled into a single network, the components panel hides all
networks (existing + draft) and lists only that network's own resources —
scoping filteredNetworks/draftNetworks to empty and filteredResources/
draftResources to the drilled network. Applies to both the category page
and search; empty sections drop out automatically. Add New keeps only the
Resource template (the network template was already hidden when drilled).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 14:52:11 +02:00
Eduard GertandClaude Opus 4.8 c5406d75cc Draft control-center: fix drill-down resource adds, empty-drill, remove draft empty state
Drill-down (single-network draft view) fixes:
- Adding a resource no longer flings the camera: highlightNetworkNode skips
  the fitView while drilled (the frame is hidden at an off-screen anchor).
- New resources no longer overlap the column: assignResourceToNetwork is
  drill-aware and places them as free top-level cards at the bottom of the
  drilled column (single writer); the drill hook only tracks them so exit
  reparents them back into the frame.
- Empty-network drill lands on the centered "No Resources / Add Resource"
  frame instead of an empty keep-set → NaN fit stuck at the dive-in zoom;
  the first resource added hides the empty-state frame.
- useNetworkFrameLayout skips all frames while any frame is drilled (its
  children are reparented out; touching them raced the reparent).
- ResourceNode hides the redundant inline "- Network" label when drilled
  into the resource's own network (reparented cards are no longer framed).

Also: remove the "Draft your changes" empty state entirely (delete
DraftEmptyCanvas + its wiring in page.tsx and CanvasContextMenu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 14:33:07 +02:00
Eduard GertandClaude Opus 4.8 0c89b2d1a4 Control-center networks header: selector + edit in both modes
- Show the network selector AND the edit (✎) as one attached control in
  both live and draft (previously live had selector-only, draft had a static
  name chip + edit). The ✎ is a right-segment; the selector squares its right
  corners when it's present. Live edit opens the real network modal (PUT) via
  the always-mounted DraftNetworkEditModal.
- Draft selector: add min-w-[200px] + popoverMinWidth so its width matches
  the live selector, and close it on canvas click (useCloseOnCanvasClick),
  like live.
- DraftModeSwitcher: show the Live | Draft segmented control in both modes as
  a persistent mode indicator (Draft active while drafting, Live switches
  back); drop the showLiveTab gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 11:52:08 +02:00
Eduard GertandClaude Opus 4.8 89b8e9ad0f Draft control-center: install-time group matching + Review & Deploy polish
Match server/agent placeholders to their installed machine by a hidden,
throwaway group instead of hostname:
- The bound group is created directly via POST /groups only when the setup
  key is generated (SetupModal.resolveAutoGroups), rides on the key as an
  auto_group, and the registering peer is matched by membership in it
  (useDraftPeerUpgrade.findByGroup; hostname stays a fallback). It's never a
  changeset entry, never shown, never used in policies.
- Naming: setup key "Draft <name>", group "<name> (Draft)" (random suffix
  only on collision).
- Artifacts (setup key + group) are cleaned up once the peer is matched, when
  the placeholder is removed, or when the draft is discarded/restarted
  (usePlaceholderArtifacts + useDiscardDraft sweep).

Review & Deploy:
- Resolving an issue (Install / assign network) no longer closes the modal —
  the fix stacks on top and returns to the changeset.
- Rows sort by the shared CHANGE_DEPLOY_ORDER so the list matches the real
  deploy order (network before its resources, deletes last); install steps
  first. Header count matches the button badge.
- install-peer rows are accordions showing the POST /setup-keys request they
  produce at install time; smaller alert-triangle, chevron on clickable issue
  badges.

Fixes:
- Connect-created policy node is centered between the two nodes (offset by
  half its size) instead of landing down-and-right of the midpoint.
- Draft exit rebuilds the live view instead of restoring a snapshot polluted
  by a mid-draft hot-reload (stale draft nodes lingering after Cancel).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 11:12:40 +02:00
Eduard GertandClaude Opus 4.8 3f5e2a29ca Draft control-center: no-network/install issues, Review & Deploy fixes, selector UX
Review & Deploy:
- A resource with no network and an uninstalled placeholder peer are now
  tracked as hard issues (getChangeIssue/hasBlockingIssues) that block deploy.
- Issue rows show an amber badge (No Network / Install) in place of the
  diffstat; clicking it opens the fix (network picker / setup modal). Issues
  sort to the top, install steps first; install-peer rows show inline steps.
- Removed the summary callout; disabled Approve & Deploy shows a "Resolve
  issues before deploying" tooltip.

Node context menu:
- "Details" -> "View Details" with an eye icon; separator before Delete.

Selectors:
- SelectDropdown gains optional controlled open/onOpenChange.
- Network selector (header) + on-canvas peer/user/group select nodes close on
  a canvas click via the shared useCloseOnCanvasClick hook (capture-phase, to
  beat the ReactFlow pane's stopPropagation); network selector gains a min
  width.

Misc:
- Components panel: networks with no resources read "No Resources".
- "No network" -> "No Network"; draft tab lightens text on hover.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-30 17:24:16 +02:00
Eduard GertandClaude Opus 4.8 a81bab850f Draft control-center: always open resource editor on add/drop
Adding or dropping a resource now opens the editor first so an
IP/CIDR/domain is entered up front; the card is created only on save
(cancel leaves the canvas untouched). All entry points route through it:
canvas "New Resource" menu, the Resource template (drop/click), a frame's
"Add Resource" button, and the frame context-menu "Add Resource".

Standalone creates land a "No Network" card at the drop/click position and
stay out of the changeset until a network is assigned. Removes the now-dead
addResourceToFrameRef plumbing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-30 16:18:47 +02:00
Eduard GertandClaude Opus 4.8 b3c141d07d Control Center: Review & Deploy changeset viewer with per-change diffs
Rework Review & Deploy into a stacked accordion of changesets, each
showing its change as a GitHub-style JSON diff of the exact API request
that will be deployed.

- Add changeset-request.ts as the single source of truth for request
  bodies; useDeployChangeset now shares these shapers so the reviewed
  diff can't drift from what is sent. buildChangeRequest/buildBeforeRequest
  reconstruct after/before payloads; toCurl renders a copyable curl using
  the account's configured management origin (cloud or self-hosted).
- Add json-line-diff.ts (LCS line diff + diffstat) and a code view with
  line numbers, +/- gutters, and a bounded scroll area.
- Accordion header: entity icon + name, request URL (click to copy as
  curl), diffstat, and a more-menu (Copy cURL / Remove-with-confirm).
- Restrict the canvas group context-menu Delete to groups that can
  actually be deleted (not IdP-issued, permitted, unused), mirroring the
  Groups page via useGroupsUsage.
- Draft start chooser: New Empty Draft / From Current View with the Group
  icon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-30 15:56:39 +02:00
Eduard GertandClaude Opus 4.8 5a2dd2c179 Draft control-center: close group/peer panel when entering a network
Entering a network — drilling into a frame (draft) or the single-network view
(live) — now closes any open group/peer Details panel, which belongs to the
view just left. Fires only on the transition in, so a panel opened while
inside a network isn't force-closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-30 10:25:54 +02:00
Eduard GertandClaude Opus 4.8 d33115424b Draft control-center: hide User Device, group-panel fixes, network assign polish
- Hide "User Device" placeholder from the canvas context menu and the
  components panel (renumber the context-menu ⌥ shortcuts); update the
  interaction-matrix / draft-networks spec docs and the e2e specs that
  exercised it (now use the Server placeholder).
- Group Details panel: derive assigned peers/resources from the changeset
  (added/removed) unioned with node data, so an SWR mutate or canvas rebuild
  no longer resurrects a removed member (panel + canvas now agree). Make the
  unsaved-changes dialog danger: title "Unsaved Changes", Discard / Cancel.
- Assigning a resource to a network that's on the canvas zooms to the frame
  and pulses its border (~2s) so the move is visible.
- Draft-created networks show a NEW badge in the "Assign a network" dropdown.
- Placeholder peers (Server/Agent) now get the blue selection ring like real
  peers (showHalo matched only data.peer.id, which placeholders lack).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-30 10:20:50 +02:00
Eduard GertandClaude Opus 4.8 1432f7f0f3 Draft entry: Create New Draft popover with blank vs current canvas
Clicking the Draft tab now opens a popover chooser (styled like the
components panel) instead of entering draft directly:

- Blank Canvas: enter draft on an empty, pannable canvas with no start
  screen (the user explicitly chose empty).
- Current Canvas: rebuild the draft from what's on the canvas, as before.

Details:
- DraftModeContext tracks startedBlank for the draft's lifetime; the
  empty-canvas overlay (DraftEmptyCanvas) is suppressed for blank drafts.
- The empty-state interaction lock no longer applies to a blank draft, so
  its empty canvas stays pannable/interactive.
- SegmentedTabs gains an activationMode prop; the switcher uses "manual" so
  onChange fires only on click (not hover/focus), fixing flicker.
- The chooser popover uses Radix modal mode for reliable outside-dismiss
  over the ReactFlow pane (which stops propagation) — no custom listeners.
- Draft tab holds its hover state while the popover is open, and is hidden
  once in draft mode (Cancel is the way back). Live tab hidden for now.
- e2e helpers updated: enter via Draft tab -> Current Canvas; exit via Cancel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-30 09:46:11 +02:00
Eduard GertandClaude Opus 4.8 d8b1cb5260 Draft mode: React-only state, drop localStorage persistence
The draft changeset and canvas already lived in React context; localStorage
was just a persistence layer on top. Remove it so draft state is context-only
for the lifetime of the session. A mid-draft page reload now rebuilds from the
live view instead of restoring the pending draft.

- Delete draft-storage.ts + its test (both localStorage keys, serializers,
  clearDraftStorage).
- DraftChangesetContext: seed changes from [] and drop the write-through effect.
- useDraft: remove the persisted-canvas restore branch and the debounced
  saveDraftCanvas effect; clean up newly-unused symbols.
- useDiscardDraft: drop clearDraftStorage calls (clearChanges suffices).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015V8ir8CjY6KWUw8QbcdBcz
2026-07-29 16:48:40 +02:00
Zoltan Papp 1d408f2e7e Add openSUSE zypper instructions with GPG key import to the Linux tab (#735)
Zypper ignores the gpgkey= entry of a .repo file, so the yum snippet would
leave the repository key uninstalled on openSUSE. The new distro entry adds
the repository with GPG checking enabled and imports the key on refresh.
2026-07-28 17:50:09 +02:00
Maycon Santos 9fab9d7a83 Cover RPM-based distros and desktop app dependencies in the Linux tab (#733)
* Cover RPM-based distros and desktop app dependencies in the Linux tab

The manual-install section of the add-peer modal only covered Ubuntu via
APT. Replace the single Ubuntu accordion with a distro selector covering
Debian/Ubuntu (APT), Fedora (DNF), RHEL/AlmaLinux/Rocky (DNF), and Amazon
Linux (YUM).

The desktop app links GTK 4.10+ and WebKitGTK 6.0 but the released
netbird-ui packages do not declare them, so each desktop-app install line
names the packages explicitly and a callout states the minimum release
(Ubuntu 24.04, Debian 13, Fedora 43, RHEL 10 with EPEL). Distros without
the required stack get CLI-only instructions.

* Add e2e coverage for the Linux distro selector
2026-07-28 03:03:46 +09:00
Eduard GertandClaude Fable 5 b523a7aa46 Live destination cards: match peer card height, show network inline
- Live destination peer cards (ResourceNode standalone branch) get the
  shared 64px card height instead of the old py-1 metrics
- Live standalone resource cards show "Name - Network" inline like
  draft (only drilled views keep hiding it - the header names it there)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:36:39 +02:00
Eduard GertandClaude Fable 5 8d66b88fea Clarify panel discard dialogs
"Discard changes to X?" read like dismissing the popup — the dialogs now
state the situation ("You have unsaved member/group changes") and spell
out that closing the panel loses the toggled assignments; the confirm
stays the short "Discard".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:30:11 +02:00
Eduard GertandClaude Fable 5 c8ba72517c Components panel: no blink on tab switch
Tab/search changes no longer remount the Virtuoso list (which painted
empty for a frame while measuring) — the mounted list scrolls back to
the top imperatively instead; the open/close remount for fresh
measurements stays.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:19:46 +02:00
Eduard GertandClaude Fable 5 ccd5440a5f Panels swap in place and virtualize; edges pass drags to the pane
- Group/peer panels: no remount on target switch (motion key removed,
  placement kept) — the slide-in only plays when opening from closed;
  search/tab/selection still reset per target
- Peers, resources, and groups lists virtualize with react-virtuoso
  through the styled scroll area (shared PanelVirtuosoScroller); lists
  remount per target so scroll offsets don't carry over
- Edges (and their wide interaction path) get pointer-events: none —
  no edge has a click action, so dragging over connection lines now
  pans the canvas; replaces the dimmed-only rule

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:15:57 +02:00
Eduard GertandClaude Fable 5 fab63e2c08 Highlight Connections polish; fix draft-entry policy column gaps
- Rename Focus to "Highlight Connections" (Waypoints icon): context menu
  item, header pill text, exit aria-label. No ring on the highlighted
  node (the dim is the signal) and no menu entry on it while active
- Availability relaxed: any node with an edge, as long as the canvas has
  at least two policies
- Dimmed edges get pointer-events: none (incl. the wide interaction
  path) so drag-to-pan works over them in highlight mode
- Draft entry from peer view: adopt live twin sizes BEFORE the build
  layout — its overlap pass measured fresh nodes with an 80px fallback
  and shoved the 60px-pitch policy column apart (huge gaps until Auto
  Arrange re-ran on measured nodes)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:03:51 +02:00
Eduard GertandClaude Fable 5 700950b452 Peer rename in canvas context menu; user-device modal says Assign
- Extract the peer page's Edit Peer Name modal into shared
  EditPeerNameModal and reuse it from the control-center context menu
  (existing peers only, permission-gated); saving PUTs the peer,
  patches canvas nodes in place, and revalidates /peers
- User-device setup modal: "Select" -> "Assign" (header, step 2, button)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:42:37 +02:00
Eduard GertandClaude Fable 5 344004f05e Align live canvas card heights and text metrics
- Peer, placeholder, select-peer, and select-user nodes all match the
  group node's 64px height
- SelectGroupNode padding py-3 -> py-3.5 (same as GroupNode)
- DeviceCard default variant adopts the placeholder card's tighter
  title/subtitle metrics so installed and pending peers read the same
- User select avatar on the peer icon-box surface (nb-gray-850)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:31:54 +02:00
Eduard GertandClaude Fable 5 77fa76d9f1 Draft mode: install steps, group sync, placeholder flows, e2e suites
Placeholder peers (user device / server / agent):
- Track pending installs as install-peer changeset entries: own "Install"
  section in Review & Deploy, resolved by install/select or removal,
  renames follow, deploy skips them (user steps, not API calls)
- User-device setup stepper modal (install NetBird -> select peer);
  the canvas node drops its inline select, upgraded placeholders always
  become plain peer cards
- Placeholders open the peer groups panel (click or Details); assigned
  existing groups ride on the generated setup key as auto_groups
- Group panel lists absorbed draft members (held on the group node) with
  NEW badge, IP/address placeholders, and alert CTAs: "Install or assign"
  / "Install" / "No network" (network picker works node-less via
  assignHeldResourceToNetwork)
- Install hostname matching also scans group-held placeholders

Groups & policies:
- Group renames and membership changes propagate into the group copies
  held by policy nodes/edges (policy-group-sync) so the edit modal and
  PeerGroupSelector stay current
- New side groups added while editing a policy join their column
  (same x, stacked below) instead of landing at the origin
- Group names: "All" reserved, duplicates rejected against canvas drafts
- Dropping into "All" rejected; All still mirrors draft peers added to
  other groups (implicit membership, never deployed)
- No-network draft resources can join groups
- Delete/Backspace routes through the context menu's Remove semantics
  (useNodeRemoval) instead of React Flow's raw deletion

Review & Deploy:
- Canvas warnings for undeployable states (placeholder-peer policies,
  unassigned resources); modal stays open on partial failure so a retry
  resumes; update-group deploys against fresh API state
- Focus mode is explicit only (context menu / header tool) - left-click
  opens panels without dimming
- Drop-eligible groups show a white border mid-drag

Tests & docs: interaction matrix spec (specs/interaction-matrix.md),
three Playwright suites (lifecycle, connections, full matrix) on a shared
e2e helper, testids across draft controls, unit tests for canvas
warnings and policy-group-sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 15:19:46 +02:00
Eduard GertandClaude Fable 5 9dec55718b Live resource edit/disable, peer Details in context menu, dialog polish
Live resources open the networks page's resource modal on click (PUT on
save) and their context menu offers Focus / Edit / Disable — every
action behind the live-mode warning; the canvas patches from the
response. Peer context menus gain Details (opens the groups panel);
live group Delete is dropped (Focus / Details / Rename remain). Dialog
confirms gain an opt-in dismissOnOutsideClick, enabled for all
control-center warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:09:57 +02:00
Eduard GertandClaude Fable 5 afad7ebbe3 Peer groups panel: click a peer to assign its groups
Clicking a peer opens the peer-side twin of the group panel — one Groups
tab listing every group ("All" excluded, draft-created ones included)
with assigned-first frozen order, search, and batch apply: draft stages
adds/removes into the changeset (canvas group nodes update in place),
live PUTs each changed group behind one warning confirmation. Same
chrome and close semantics (ESC keycap, discard guard, pane click), and
the clicked peer wears the panel halo — selectedPeerPanel lives in the
narrow DestinationGroup context so PeerNode can ring without
subscribing to CanvasState.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 11:53:06 +02:00
Eduard GertandClaude Fable 5 5f352614c8 Order drilled resources like frames; size network selector to its labels
The live single-network view now runs its resource column through
orderFrameResources, so the drilled view, the draft drill-down and the
overview frames all agree (policy-reached resources first). The network
selectors (live + draft drill-down) size to their longest option label,
clamped to the old 256px as max.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 11:33:41 +02:00
Eduard GertandClaude Fable 5 65b8f59031 Focus via context menu, live group actions, drilled group order parity
Focus Mode is entered from the node context menus ("Focus", shown only
where it declutters — 4+ edges and 2+ policies in the neighborhood, via
isFocusWorthy) or the "F" shortcut; the header button is gone and the
top-center pill owns the key handling. Live right-click now also covers
groups (Details / Rename / Delete behind live-mode warning confirmations,
PUT/DELETE + canvas patch) and peers/resources (Focus).

The shared drilled layout sorts both group columns by name so the live
single-network view and the draft drill-down agree on the order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 11:25:51 +02:00
Maycon Santosandbraginini 6ca91287fc Show agent-network prompt-cache tokens and cost on hover (#731)
Co-authored-by: braginini <bangvalo@gmail.com>
2026-07-26 21:43:33 +02:00
Eduard GertandClaude Fable 5 515ab88182 Rework focus mode into an explicit header tool; restore policy click-to-edit
Focus Mode is now armed from a header button next to the mode switcher
(SquareDashedMousePointer, sky accents, "F", switcher-style shell): the
next node click picks the focus target — hover previews the ring, node
buttons are inert, selectors/edge-less nodes are unfocusable — then the
mode goes sticky: the target keeps a glowing sky ring (cc-focus-root),
on-path nodes stay clickable/movable (dimmed ones lock), group panels
open without stealing focus, and only Esc / the top-center pill's X /
the button exit. The pill (switcher surface, fully rounded) names the
focused node. Fit-view moves to "1" to free F.

Policy left-click opens the editor again (live and draft); the context
menu keeps Edit/Disable. Network frames order policy-reached resources
first, via one shared helper so live and draft frames agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 18:19:19 +02:00
Eduard GertandClaude Fable 5 102891784e Add a Beta badge to the Draft tab in the mode switcher
Same SmallBadge treatment as the sidebar's Reverse Proxy entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:33:02 +02:00
Eduard GertandClaude Fable 5 523095d3bd Rebuild the group panel as a membership editor with checkbox assign
The destination group panel becomes a search + Peers/Resources tabs
editor (PeerGroupSelector-style rows and checkboxes): toggles edit a
local selection previewed on the node's count text, and Assign (draft)
stages ONE coalesced update-group change while Save (live) PUTs behind
the live-mode warning. Implicit closes (Esc, keycap, pane click) confirm
before discarding unassigned toggles via a shared close guard; Cancel
discards directly. The changeset/deploy pipeline learns member REMOVALS
(removedPeerIds/removedResourceIds + removedMembers node sets).

Panel chrome: components-panel surface, right-anchored full-height with
header-width tracking (in-place on width changes — no flash), toolbar
spring slide-in, singleton mount, frozen row order per open, and the
canvas pans the group node clear of the panel. Focus mode is skipped for
nodes without connections, and mode switches close the panel and drop
node selection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:30:52 +02:00
Eduard GertandClaude Fable 5 5a113b6b64 Reset the components panel scroll when switching category tabs
Virtuoso keeps its scroll offset across data swaps, so changing the
category (or entering/leaving search) landed mid-list at the previous
tab's position. Remount the list on category/search-mode changes, like
the existing open/close remount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:27:02 +02:00
Eduard GertandClaude Fable 5 25cc20efcc Pin the draft frame grid to the live overview's fixed midline
The draft build centered the frame grid on the measured midpoint of the
source/policy columns, which always landed a few px off the live
overview's fixed midline — and the live-frame anchor shift then pushed
that delta onto the columns, so peer/policy cards hopped a few pixels on
every live↔draft switch. Use the same fixed midline as live
(SOURCE_NODE_HALF_HEIGHT) so the geometry matches exactly and nothing
moves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:35:30 +02:00
Eduard GertandClaude Fable 5 e23272d163 Align the networks overview's columns with the other views' anchors
Policies move from x 480 / -17 center offset to the shared x 500 / +14
anchor, sources drop their -30 half-height shift, and the frame grid
centers on the columns' midline instead — so a policy and its source
keep their world position when switching between the peer/user/group
and networks views. The draft build layout mirrors the same change to
keep entry/Auto Arrange parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:27:40 +02:00
Eduard GertandClaude Fable 5 80254c7d9a Keep the focused node draggable in focus mode
Focus mode turns global nodesDraggable off so dragging pans, which also
froze the focused node itself. The dim effect now stamps a per-node
draggable: true on the focus root (overrides the global flag) and strips
it when focus ends — works for policy/peer/resource focus and the group
panel alike.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:20:27 +02:00
Eduard GertandClaude Fable 5 dd08aea531 Focus policies/peers on click; live policy context menu with Edit + toggle
Clicking a policy or peer now enters focus mode (path highlight + blue
ring) in live and draft instead of opening the policy modal. Policy
editing moves to the right-click menu: in live it offers Edit and
Disable/Enable only — both behind a "you are in live mode"
confirmation — applying via updatePolicy and patching the canvas in
place through refreshLiveViewRef. No Delete in live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:16:40 +02:00
Eduard GertandClaude Fable 5 63abf6bbfb Keep parents before children on the controlled canvas
On a controlled flow, instance.setNodes round-trips through
applyNodeChanges, which keeps replaced nodes at their ORIGINAL index —
so reparenting a resource that predates its frame (network drop
adoption, assign-to-network) left the child in front of the frame and
ReactFlow dropped the containment: correct parentId in state, card
rendered detached at frame-relative coordinates.

Reconcile the invariant centrally in onNodesChange (and heal persisted
snapshots saved with the bad order on draft restore). Also stamp the
owning network on policy-drop destination resources so they don't show
"No Network".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:49:17 +02:00
Eduard GertandClaude Fable 5 5605ff4eed Adopt pre-assigned draft resources when their network lands on canvas
Assigning a resource to a network that isn't a frame yet stamps only the
ref; dropping that network later now adopts those resources into the
frame (and the create-network assign waits for the frame to reach the
ReactFlow store instead of racing it on a 0ms timeout).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:49:05 +02:00
Eduard GertandClaude Fable 5 1b29d44146 Make Auto Arrange object-aware: resolve node overlaps after layout
Stragglers outside every layout column (e.g. a standalone resource in a
frames draft, type-bucketed at x 1400 inside the frame grid) could land
on top of another node. Add resolveNodeOverlaps to the shared draft
build layout: larger nodes anchor, smaller intersecting nodes are nudged
the shortest way clear plus a 40px margin, re-checked until free.
Detection is exact intersection so tight column rhythms keep entry-layout
parity. Unit-tested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:21:41 +02:00
Eduard GertandClaude Fable 5 3516fa6f42 Make Auto Arrange reproduce the initial layout in every view
Extract the draft build layout into utils/draft-build-layout.ts
(applyDraftBuildLayout) and use it for the toolbar's Auto Arrange, so
arranging an untouched draft matches the entry layout exactly for drafts
entered from any live view; drill-downs re-run the shared drilled layout.
Delete the divergent applyDraftArrangeLayout and its height-aware pitch
machinery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:15:16 +02:00
Eduard GertandClaude Fable 5 d103f171ea Polish the Assign a network modal
- Footer buttons follow the shared modal pattern (right-aligned, gap-3)
  with the header's separator color; CTA renamed Save -> Assign Network.
- Network select: search inside the popover, popover matches trigger
  width, list capped at 190px, placeholder "Select or create a
  network...".
- New optional `footer` slot on SelectDropdown: content pinned below the
  scrollable options (always visible regardless of scroll/search) — used
  for "Create New Network", styled like a regular entry.
- Creating a network opens the Add Network form ON TOP of the picker
  (nested modal); Cancel returns to the picker instead of closing both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:59:24 +02:00
Eduard GertandClaude Fable 5 8583fd9175 Gate canvas deletion: nothing in live, no standalone edges in draft
onBeforeDelete on the ReactFlow canvas: live mode blocks Backspace/
Delete entirely (the canvas mirrors the account); draft mode keeps node
deletion (edges cascade with their endpoints) but never deletes a
selected connection on its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:48:57 +02:00
Eduard GertandClaude Fable 5 d43c51495d Center-align source/policy columns with network frames
The networks view stacked source and policy node TOPS on the column
midline while the frame grid centers frames on it, leaving a lone peer
~30px and a policy pill ~17px below their frame. Both the live overview
and the draft rebuild now subtract shared half-height constants.

Draft fixes this exposed: packFrameGrid also runs for a single frame
(previously left at the far layout column, so the live-anchor shift
dragged the columns ~350px out of line), and the frame-grid midline
falls back to the live twin's measured height instead of an 80px guess.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:39:44 +02:00
Misha Bragin 5a5e04ad35 Add 0.75 announcement (#727) 2026-07-23 21:05:52 +02:00
Maycon Santos 598ecb196d remove rc releases logic for agent networks (#725) 2026-07-23 20:29:09 +02:00
Maycon Santosandbraginini 7c8352822d Add Kimi (Moonshot AI) provider support to Agent Network UI (#724)
* Add Kimi (Moonshot AI) provider support to Agent Network UI

Pairs with the management catalog's new kimi_api entry:

- kimi_api in the AIProviderId union and the access-log provider-id
  normalization list.
- Provider modal: api.moonshot.ai host placeholder, upstream-URL help
  explaining the OpenAI-shape (/v1) vs Anthropic-shape (/anthropic)
  path choice, sk-... key placeholder.
- Configure Your Agent: Kimi backend option in the Claude Code tab
  (JSON + shell modes), a Kimi variant in the Codex tab using
  wire_api="chat", and a new Kimi CLI tab with the ~/.kimi/config.toml
  provider setup. All Kimi surfaces render only when a kimi_api
  provider is connected, and pre-select themselves when Kimi is the
  only provider of its shape.
- Onboarding defaults to the Claude Code tab for Kimi providers.

* Add Kimi provider e2e spec

Walks the Kimi lifecycle against the real backend: select kimi_api from
the catalog (prefilled host, catalog model pricing), connect it, then
assert the Kimi-gated Configure Your Agent surfaces (Kimi CLI tab,
Claude Code kimi-k3 config, Codex wire_api=chat variant) and clean up
via new agent-network API helpers. Skips when the management build
under test has no kimi_api catalog entry, so it can merge ahead of the
backend and activate once the management-cloud image carries it.

* Remove Codex option for Kimi and align Claude Code config with Moonshot guide

Codex has no clear support against Moonshot's upstream, so the Kimi
variant is dropped from the Codex tab. The Claude Code Kimi snippet now
pins all default model tiers and the subagent model to kimi-k3 and
disables tool search, per Moonshot's Claude Code guide.

* Carry the /anthropic shape prefix in the agent base URL for Kimi

The Kimi provider keeps its default https://api.moonshot.ai upstream;
Claude Code and Kimi CLI select the Anthropic shape by appending
/anthropic to the endpoint base URL, which rides through to Moonshot.
One provider now serves both API shapes.

* Kimi CLI uses the bare endpoint base URL

Only Claude Code carries the /anthropic prefix; Kimi CLI's anthropic
provider type points at the bare endpoint.

* Kimi tooltip

* Make the Claude Code provider dropdown width flexible and tidy Kimi notes

* Make the Claude Code provider dropdown width flexible and tidy Kimi notes

* Restored the note on the Kimi CLI tab

---------

Co-authored-by: braginini <bangvalo@gmail.com>
2026-07-23 19:24:41 +02:00
Eduard GertandClaude Fable 5 47a01c68c1 Unify column spacing and kill the live<->draft mode-switch flicker
- Source columns (live peer/group/user views, user-view peers, draft
  rebuild) share the destination column's 100px pitch instead of 80/120.
- Live destination resources show "Name - Network" inline like draft
  (network ref stamped in addDestinationResourceNodes).
- Draft rebuild: dual-role groups (source AND destination) restack in
  the destination column like the live view shows them.
- Mode-switch flicker: React Flow hides unmeasured nodes for one frame,
  blanking the swapped canvas. Draft nodes now adopt their live twin's
  measured size (initialWidth/Height), including renamed ids (self-ref
  dest-group clones, destination resources/peers).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:04:01 +02:00
Eduard GertandClaude Fable 5 966d7bca36 Group view: show policies where the group is a destination
Policies were matched by rule.sources only, so a destination-only group
rendered an empty view. Those policies now render mirrored to the left
(sources -> policy -> selected group); source groups carry explicit
enabled state so they don't fall back to the incoming-edge check and
render dimmed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:38:25 +02:00
Eduard GertandClaude Fable 5 e3ef24e9bd Rewrite control-center CLAUDE.md: 326 → 67 lines
Keep architecture, layout/sort parity rules, changeset/deploy facts, and
the hard-won perf/camera gotchas; drop per-file trees and feature
narration discoverable from code. Fix stale claims (change types,
deploy order, provider names, focus-mode scope).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:27:25 +02:00
Eduard GertandClaude Fable 5 e79de5e317 Match draft rebuild layout/order to the live view it was entered from
Peer/group/user-entered drafts used the networks-overview rhythm (160
base, policies 480/90) and scattered destinations across layout buckets.
Now frameless drafts use the live views' spacing (120 + DEFAULT_LAYOUT_
CONFIG), restack destinations as one x1000/100 column ordered like live
(groups first, then resources/peers by first policy edge), sort policies
with the views' sortBy, and name-sort destinations on group-view entry.
Existing destination resources get their real network stamped (no bogus
"No Network" badge), and the live destination-peer card sizes to content
(w-auto) like the draft peer card.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:21:30 +02:00
Eduard GertandClaude Fable 5 a6f43c8ef0 Fix top-left node flash when returning to control center
On client-side nav back to the page, nodes commit before fitView's
measurement wait completes and painted at the default viewport. The
first fit after mount now hides the viewport (cc-prefit, instant hide),
snap-fits without animation, and fades the scene in already framed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:57:45 +02:00
Eduard GertandClaude Fable 5 cc0455d4a5 Unify live/draft network view: interactive live frames, matching layout
- Live frames gain the draft chrome for real: Add Resource opens the
  networks ResourceModalContent (real POST), routing bar Add + zero-state
  open the real RoutingPeerModalContent; the Add band is reserved in both
  modes
- Live frames contain destination resource-group rows like draft
  (per-network ids); peer-only destination groups are skipped in the
  networks view (live never drew them; networks-entered drafts drop them
  + their edges); draft group folding matches canDropGroupIntoNetwork
  (one member resource in exactly one network, peers don't block)
- Identical geometry and order across modes: shared FRAME_GRID_BASE_X,
  frame heights re-seeded after folding, group rows first, sources
  (groups + source peers) as ONE 160-pitch column, policies at x480/90,
  every column + the frame grid sorted by name
- Routing-peers dropdown polish: rows prefetch on trigger hover (no
  skeleton flash), skeleton geometry matches the final list, exact list
  height (no scrollbar for short lists), tighter search-to-rows gap

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:48:57 +02:00
Eduard GertandClaude Fable 5 f3d4ef75ae Focus mode works in draft mode too
useGroupFocusDim and the focus drag-lock no longer gate on live mode;
className joins the history's transient keys so focus toggles never
create undo entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:05:22 +02:00
Eduard GertandClaude Fable 5 b3dde3f6a2 Live focus mode + policy nodes in networks overview; drop user-detail view
- Networks overview builds source -> policy node -> network frame with
  smart edges (like draft) instead of labeled straight lines; sources
  left, policies middle, packFrameGrid right; frames whose policies are
  all disabled dim (frame + child rows)
- AnimatedLine is a SmartEdge-style bezier with a midpoint label chip
  (orthogonal channel router removed); drill-out reveal zooms out
- Focus mode (live): clicking a group opens its panel, clicking a
  peer (user view) or resource card sets focusedNodeId; everything off
  the node's path dims to grayscale (forward+backward closures) via
  cc-dimmed className; dimmed nodes are non-interactive and clicks fall
  through to the pane which exits focus; node dragging disabled while
  focused (dragging pans; per-node draggable:true removed - it
  overrides the global flag)
- User-detail view removed: previousSelectedUser breadcrumb,
  forceSinglePeerView/forceSingleUserView, user-peer history level,
  onForceSingleUserView
- Bigger frame grid gaps (280/200)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:03:27 +02:00
Eduard GertandClaude Fable 5 93010cd3d7 Drill-out reveal zooms out instead of growing in
The fly-out's reveal reused the drill-in's grow-in (0.7x -> fit), which
read inverted going back; direction "out" now starts at 1.45x and
settles outward, mirroring the dive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:56:12 +02:00
Eduard GertandClaude Fable 5 34b2ce3493 Fix whole-canvas re-renders: stable ReactFlow props + memoized contexts
React profiler showed ~2700-fiber User-Blocking commits (120-170ms) on
every drag tick, hover flip and click. Root causes and fixes:

- DraftModeProvider context value memoized (hover state changes handed a
  fresh value to every useDraftMode consumer = every node)
- Every callback/object prop on <ReactFlow> is identity-stable via
  useStableHandler; inline callbacks defeated GraphView's memo so each
  nodes update re-rendered the entire canvas subtree
- selectedDestinationGroup split into its own context (group click
  re-rendered every node via useCanvasUI)
- Edges subscribe to endpoint rects via useEdgeNodeRect (value equality)
  instead of useInternalNode; ResourceNode's parent-hidden check likewise
- elevateEdgesOnSelect off (edge DOM reshuffle restarted dash animations)
- Draft persistence + history capture skip while dragging and pre-check
  structurally before stringifying; history ignores transient node state
- Lazy router-rows fetch for draft frames too; Add Resource button uses
  a ref instead of mounting useControlCenterData per frame
- Components panel, PeersToolbar, DestinationGroupPanel, CanvasToolbar,
  DraftEmptyCanvas subscribe structurally (useStructuralNodes), not to
  the churning canvas context
- Drag drop-target highlight throttled to ~8px steps
- Live networks overview arranged like draft (sources left, packFrameGrid
  staggered frame grid right; d3 force layout removed from this view)
- Draft build: resource-only destination groups fold INTO their network
  frame; frames seeded with the capped grid; useAnySourceGroupEnabled
  skips when enabled is explicit

Verified via Chrome trace on the live app: 4ms/s JS during a 125Hz drag
over 36 network frames, no long tasks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 14:50:43 +02:00
Eduard GertandClaude Fable 5 be6744aa18 Live view policy destinations render as cards
destinationResourceNode gets standalone: true in the shared builder —
resource destinations route to the StandaloneResourceNode card, peer
destinations get the same card surface as PeerNode's card variant
(previously both were transparent rows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:44:20 +02:00
Eduard GertandClaude Fable 5 6eec46e37c Draft overview network selector + drill history survives mode switches
Draft mode (not drilled) shows the top-left network SelectDropdown like
live: "All Networks" first, canvas frames sorted by name, picking one
drills in. Drill history entries are consumed deferred (150ms) so the
transient deactivation during draft/live switches reuses the entry
instead of racing history.back() — browser Back after switching modes
now exits to the overview instead of leaving the page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:24:26 +02:00
Eduard GertandClaude Fable 5 a9836381d4 Hide peer connect handles in live networks view
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:13:19 +02:00
Eduard GertandClaude Fable 5 5882d95343 Browser Back exits canvas drill-downs; lighter simple connection lines
useDrillDownBrowserHistory tracks a stack of drill levels (network
drill-down, user-to-peer breadcrumb): entering pushes one same-URL
history entry, popstate exits through the same path as the UI back
arrow, UI exits consume the entry silently. SimpleConnection stroke
lightened from #595959 to #7d7d7d.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:08:57 +02:00
Eduard GertandClaude Fable 5 6526f72f4d Fix misaligned camera when returning to control center via client nav
Warm SWR cache makes the view initialize before ReactFlow measured the
freshly remounted nodes; fitView now retries (bounded rAF loop) until
every target node has a measured size before fitting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:54:58 +02:00
Eduard GertandClaude Fable 5 8aebc55be2 Group view initial pick falls back to All when only All has policies
getFirstGroup excluded All from the has-policies check, so an empty
non-All group could win over a populated All view. New order: non-All
group with policies, then All with policies, then any non-All, then any.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:51:54 +02:00
Eduard GertandClaude Fable 5 aeea740bd2 User view peers render as draft-style cards without connect handles
PeerNode now always renders the invisible AllHandles edge anchors
(GroupNode's pattern) so showHandles:false only hides the visible
connect bubbles instead of detaching edges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:39:39 +02:00
Eduard GertandClaude Fable 5 7c0f6b4d66 Live policy saves patch the canvas in place from the PUT response
No more waiting on the SWR /policies revalidation (or a layout reset):
refreshLiveView rebuilds the current view with the saved policy spliced
in, keeping positions and camera; all view appliers accept a
policiesOverride and the policy modal reads the canvas node's policy
first in both modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 09:29:26 +02:00
Eduard GertandClaude Fable 5 9908a5ddcb Shared drill-down canvas transition (dive/fly-out) for draft and live
utils/canvas-transition.ts: one continuous zoom stitched across an invisible
swap — accelerating dive/zoom-out + fade-out (half ease-in), swap while
transparent, grow-in reveal + fade-in (half ease-out) into a viewport
computed from the new scene. One-liner facades drillInto/drillOutOf (Node or
Rect targets) for any future drill-down; isCanvasTransitionActive suppresses
view-init fitViews mid-transition.

- Draft drill-down enter/exit refactored onto it (position-aware close-up on
  exit, exitingRef guards the reconciling repair; frame layout + edge
  attachment + row/card rendering key off frame.hidden so nothing morphs
  while visible)
- Live network select dives into the clicked frame (dropdown picks zoom from
  center), back flies out to the overview

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:54:02 +02:00
Eduard GertandClaude Fable 5 acabf71011 Exiting draft mirrors the draft's drill state in live
Backed out of the drill inside draft → live lands on the all-networks
overview instead of the stale drilled snapshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:41:19 +02:00
Eduard GertandClaude Fable 5 9e5816c5a4 Unify the single-network (drilled) layout across live and draft
- utils/drilled-layout.ts: ONE layout (applyDrilledLayout, 95px resource
  pitch, getDrilledFrameAnchor incl. the column's +5 centerY offset) shared
  by the live single-network view, the draft drill-down, and live→draft
- Draft drill re-lays the kept world out with it (parent positions
  snapshotted, restored on exit with hidden flags + viewport); drilled frame
  grid is a fixed-pitch single column, cards auto-size (no forced width)
- Live networks render as read-only frames (RoutingPeersBar without Add,
  frame anchors/handles for edges, hover highlight, click/row-click drills,
  bottom padding instead of the draft Add band); drilled resources render as
  standalone cards in live too, with direct policy→resource edges
- Entering draft from live drilled carries the network as a frame and enters
  the draft drill-down in the same commit (pre-hidden, no flash); exiting
  draft while drilled lands in the live view of that network
- Live all-networks lines are blue with an All fallback label; peer sources
  drawn in both live network views; NEW badge only for id-less networks
- Drill header: name chip matches the live selector (icon size/color,
  typography, paddings), edit segment for all frames (existing networks open
  the real network modal), RoutingPeersBar height/chevron aligned with the
  fixed-height network selector; SelectDropdown defers onChange past its
  close animation (no blink) and uses a 16px chevron

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:14:07 +02:00
Eduard GertandClaude Fable 5 48e899ed65 Shared RoutingPeersBar for draft frames and the live network header
Extracts the frame's [status | Add] routing button group + searchable
routers popover into RoutingPeersBar (indicator, row type, peer-count and
sort helpers). NetworkNode consumes it unchanged; the live single-network
header's NetworkRoutingPeerCount now renders the same bar over the API
routers, with Add/edit navigating to the routing-peers tab. Status button
left padding 14px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:37:13 +02:00
Eduard GertandClaude Fable 5 fc68538457 Frame's Add Resource button drops a blank row instead of opening the editor
Matches the context menu's Add Resource (addResourceToFrame); the editor
still opens on row click.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:14:40 +02:00
Eduard GertandClaude Fable 5 b994d703ff Network frame routing-peers dropdown
- Frame status button gains a ChevronsUpDown trigger and a PeerSelector-style
  popover (search by peer/group name, no enter badge): one row per router with
  muted OS icon + peer name or group badge + group name (peer count inline)
- Draft router rows open the routing-peer modal prefilled (editChangeId on
  routingPeerModal); save replaces the create-router change
- Status counts peers, not routers: groups contribute their peer count,
  disabled routers contribute nothing and render at 50% opacity, sorted last
- Floating bar nudged 2px left; NetworkRouter API routers of existing
  networks are listed too (read-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:08:18 +02:00
Eduard GertandClaude Fable 5 59af25d45c Groups can drop into network frames; selection, count-label and grouping fixes
- Drag a group onto a network frame → converts in place to a resource-group
  row inside it (same node id, policy edges re-attach to the frame). Eligible
  when the group is empty, has a resource already in that network, or carries
  unassigned draft resources (canDropGroupIntoNetwork, tested)
- Grouped standalone draft resources ride on the group node (data.draftResources);
  dropping the group into a frame records create-resource changes assigning
  them to that network with the group membership
- Frame children are selectable:false — full-width rows no longer join any
  grazing rubber-band selection (phantom Create Group members)
- Create Group resolves draft resources via getDraftResource so their new-…
  ids reach the group (count label, changeset membership, policy regrouping)
- Group count label leads with resources: "2 Resources, 1 Peer"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 11:24:00 +02:00
Eduard GertandClaude Fable 5 ee7f1d1dfb Resource-sourced drags onto peers/groups open the policy modal flipped
Dragging from a resource onto a peer/group prefills the create-policy modal
with the resource as destination and the peer/group as source (framed
resources keep the network destination scope). Also: context-menu Add
Routing Peer uses Share2Icon, NetworkNode Add buttons use CirclePlusIcon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:56:27 +02:00
Eduard GertandClaude Fable 5 d6bc80670f Dropped and dragged nodes settle on top of the canvas z-order
Shared getTopZIndex (max settled z + 2, beating frames and their children)
applied in placeNode, dropExistingNetworkFrame, addNewGroup, and
onNodeDragStop — e.g. a peer dropped over a network frame paints above it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:35:49 +02:00
Eduard GertandClaude Fable 5 e8ba1d7378 Policy Remove empties its sources/destinations; re-drop uses draft state
- Removing an existing policy records an update-policy change with emptied sources/destinations (draft policies still just cancel their pending create)
- Components panel re-drop (and network/resource auto-drawn policies) prefer the pending update-policy state over API data, so a disconnected policy doesn't come back with its deployed sides

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:29:01 +02:00
Eduard GertandClaude Fable 5 1f6028253e Draft policies: pull in resource policies on drop, canvas-only policy Remove, delete confirms
- Dropping an existing network/resource also draws the API policies targeting its resources (getPoliciesTargetingResources + drawResourcePolicies)
- Policy context menu Remove (CircleMinus, no confirm) removes only the policy node and edges, keeps sources/destinations on canvas, cancels pending create / drops pending updates
- Deletes (policy/resource/group) confirm: marked for deletion, deleted on Review & Deploy
- Group node removal strips the group from connected policies (like peers/resources); deferred policy sweep skips policies removed in the same cascade
- Remove icons switched to CircleMinusIcon; unit tests for the policy matcher

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:15:09 +02:00
Eduard GertandClaude Opus 4.8 b5e3d8ff88 Fix group node "Details" being immediately dismissed
The context menu's item-click and outside-click shared one onClose that
cleared the panel too, so "Details" opened the group panel then instantly
closed it. Split into onClose (menu only, for item clicks — stopPropagation so
it doesn't reach the document listener) and onDismiss (everything, for outside
click/scroll). Details now opens the panel and it sticks; outside click still
dismisses menu + panel in one click.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 17:09:15 +02:00
Eduard GertandClaude Opus 4.8 3a3b0785c3 Fix live hierarchical layout alignment regression
centerNodesVertically is shared by the live hierarchical layout and the draft
auto-arrange; the height-aware pitch added for tall network frames broke live
column alignment (unequal per-node pitches). Make height-aware pitch opt-in
(auto-arrange only); live keeps a fixed pitch so equal-height nodes stay in a
straight line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 17:03:52 +02:00
Eduard GertandClaude Opus 4.8 0913a6aa2c Standalone resources, existing-network frames, resource changeset ops
- Dropped resources are standalone cards with an inline network label / "No
  Network" picker (DraftResourceNetworkModal); assign via drag-into-frame or
  the picker; extract StandaloneResourceNode
- Existing networks drop as full frames (dropExistingNetworkFrame) with their
  resources as children; reparent an already-dropped resource instead of
  duplicating its node id
- Refactor frame-ness to an explicit data.frame flag (isFrameNode /
  isDraftNetworkNode); network-new- now means only "draft network", existing
  frames keep their real id
- Resource context menu: Edit (all), Enable/Disable, Rename (draft), and
  Remove vs Delete (existing framed) — wired into the changeset via new
  update-resource / delete-resource change types + deploy PUT/DELETE; draft
  enabled folds into create-resource
- Components panel: existing resources show "name - network" and can't drop
  into a frame; resource drags highlight the target frame (white border)
- Frame z-layer so no node renders between a frame and its resources
- Auto-arrange stacks columns by measured node height
- Group node "Details" menu item opens the panel; policy left-click no longer
  leaves a halo; misc spacing/badge/icon tweaks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:57:21 +02:00
Eduard GertandClaude Opus 4.8 aa8be0655d Network frame refinements: resource groups, spacing, auto-arrange heights
- Add "Add Resource Group" context-menu item (default group icon); blank
  group rows drop first in the frame and count toward layout/overflow
- Group naming now consistent ("Group", "Group (2)", …) everywhere
- Resource groups: Rename + Details context menu, sky halo on right-click,
  hover-only white ring during connects, "No Resources" count label
- Group node context menu gains "Details" (list icon) to open the panel;
  clicking a group no longer toggles the panel closed (only outside click)
- Newly added resources/groups always land first in the frame grid
- Auto-arrange stacks columns by measured node height so tall network
  frames (with floating routing bars) don't overlap neighbours
- Tune Add Resource button spacing (top gap + bottom padding)
- Remove routing-peer HelpCircle + HA tooltip; nudge "+N more" cell 1px
- Fix User Device select node NEW badge alignment

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 12:09:57 +02:00
Eduard GertandClaude Opus 4.8 1471767e27 Network frame: floating Add/routing controls + bottom Add Resource button
- PeerNode: server/agent placeholder Install button uses the shared Button
  (secondary, xs) to match the User Device select node.
- NetworkNode: rework the frame's floating controls — an "Add" resource
  control and a routing-peers button group ([● status | + Add]) sized to
  match the Install button (secondary, xs).
- Add a secondary "Add Resource" button in the frame body: centered when
  empty, full-width bottom row once resources exist; opens the resource
  editor. Reserve a bottom band for it in the frame layout (mutually
  exclusive with the "+N More" overflow footer).
- Sparse frames (0/1 resource) match the two-resource width; empty frames
  get a little extra height so the centered button breathes.
- Hovering the floating controls / add button no longer highlights the
  frame or reveals its ConnectHandle (new ConnectHandle `hidden` prop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 17:28:13 +02:00
Maycon Santos e4f5046ff2 [agent-network] per-provider disable-metadata toggle (#715) 2026-07-20 14:46:17 +02:00
Eduard GertandClaude Opus 4.8 d5f61efc1a Fix network resource policy creation with single peer source (#721)
When creating a network resource and adding a policy with a single peer
(sourceResource) as the source, the payload included both an empty
`sources: []` array and `sourceResource`, causing the backend to reject
it with "specify either sources or source resources not both".

`createPolicyForResource` always computed `sources` as an array while the
`...rule` spread carried `sourceResource` through. This mirrors the
existing destinations handling: gate `sources` on `rule.sourceResource`
so it becomes undefined (and is dropped during serialization) when a
source resource is set.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 11:16:36 +02:00
Maycon Santos 3503141c56 Adjust license cache ttl to 10 minutes (#718) 2026-07-20 11:03:14 +02:00
Eduard GertandClaude Opus 4.8 b26c6f0a12 Network frame: header Add dropdown + "+N More" overflow footer
Rework the draft network frame's resource affordances:

- Move adding into a header "Add" dropdown (chevron) on the right of the
  name/count, with Routing Peer (server icon) and Resource (resource
  icon) items. Removes the separate "Add" button from the floating
  routing-peer status pill and drops the always-present "Add Resource"
  child row (AddNetworkResourceNode deleted).
- Summarize resources past the visible cap with a "+N More" footer
  rendered by NetworkNode in a band useNetworkFrameLayout reserves at the
  frame bottom (pointer-events-none, so clicking still drills in),
  replacing the fade mask and the short-lived ResourceOverflowNode.

useNetworkFrameLayout is simplified to lay out only resource children and
sweeps legacy add-resource/overflow child nodes from persisted drafts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 10:27:31 +02:00
Eduard GertandClaude Opus 4.8 b1824b50ba Add networks empty with an always-present Add Resource row
New draft networks are created without seeded resources: drop the
layout-testing seed (30 fake resources + 10 resource groups) from
addDraftNetwork so a network starts empty.

Every network frame — including a freshly-added empty one — now always
contains the "Add Resource" child row: useNetworkFrameLayout creates it
if missing and no longer removes it when the frame is empty (dead
obsoleteAddRows path removed). The centered "No resources yet" hint is
gone; the header subtitle reads "No Resources" when empty and the frame
keeps its header separator.

Also removes the bottom-fade gradient overlay from the existing-network
card's resource preview grid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 09:41:49 +02:00
Eduard GertandClaude Fable 5 9bfb73231d Draft editors, context menus, components panel & selection toolbar
- Resource editor gains a create-mode (frame's Add Resource row opens the
  modal; the node is only created on save) and the provider nesting fix
  for the resource-modal refactor; new DraftNetworkEditModal (name +
  description) shared by the network context menu and drill-down header.
- Context menus: Edit items (SquarePen) for policies/networks/resources,
  policy Remove vs Delete split (draft vs API), PencilLine renames,
  Add Resource on frames, Policy above Group ordering.
- Components panel: draft peers/policies/networks listed with NEW badges
  (IP placeholder, kind icons), "Networks & Resources" category with
  existing networks droppable as live cards, virtualization measurement
  fixes, panel hides once a real drag starts.
- Selection toolbar: Remove option next to Create Group, padding fix.
- Auto-names drop the "New " prefix ("Group", "Resource").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:54:06 +02:00
Eduard GertandClaude Fable 5 987054f765 Network frames v2: destination picker, drill-down, routing modal, layout
Spec §10 implementation plus the frame interaction model:

- Connections: peer/group drops onto a frame or its contained rows open
  the create-policy modal (source prefilled, destination scoped to the
  network, one-way); POLICY↔network drags open the minimal destination
  picker (DraftNetworkDestinationModal). Drag no longer creates routers.
- Policy edges to framed resources attach to the frame in the parent view
  and to the actual resource in the drill-down (useFrameEdgeAttachment,
  pure logic + tests in utils/frame-view).
- Drill-down: clicking a frame enters a live-mode-like single-network view
  (no frame box, square-ish grid, back/name/edit/routing-count header);
  hidden flags + viewport restore on exit.
- Routing peers: the frame button group and context menu open the networks
  page's routing-peer modal; routers are changeset-only (no canvas nodes),
  counts derive from create-router changes, deploy sends the modal's
  metric/masquerade/enabled.
- Frame layout: 4x2 row-major grid capped at 6 visible resources with a
  fade-out on the last row, always-visible Add Resource row, tunable
  padding/gap/width constants, drop z-order fix for stacked frames.
- Node visuals: NEW badges after draft names, frame-hover highlights
  header/border/icon boxes, placeholder select copy, min widths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:53:51 +02:00
Eduard GertandClaude Fable 5 71cc5c038d Pure-data modal modes and draft-aware props for shared components
- NetworkModal, NetworkRoutingPeerModal and NetworkResourceModal gain a
  useSave={false} mode: no API calls, values return via onSaved — the
  draft canvas reuses the exact networks-page modals.
- AccessControlModal: destinationScope restricts the destination selector
  to a network's resources/groups (no peers) and locks the policy one-way.
- PeerGroupSelector: groupIds whitelist (disables inline group creation),
  initialTab (open tab without reordering triggers), scoped empty states,
  and a default icon for type-less draft resources.
- ResourceBadge/PeerOperatingSystemIcon: default resource icon and
  Server/Agent/User-Device icons for draft placeholder peers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:53:31 +02:00
Misha Bragin a27e4654cf Fix IdP cards spacing (#717) 2026-07-16 14:41:59 +02:00
Eduard GertandClaude Fable 5 991ee63b6d DRY up NetworkNode and the full-area target handle
NetworkNode: RoutingPeersIndicator and AddRoutingPeerButton extracted
(shared by the draft frame and the live card). The invisible
whole-node target handle becomes handles/FullAreaTargetHandle, reused
by AllHandles, PolicyNode, and NetworkNode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 14:20:59 +02:00
Eduard GertandClaude Fable 5 869ae3af02 Dragging a contained resource moves its network frame
Frame children are draggable again (non-draggable nodes emit no drag
events — the gesture fell through to canvas panning). A drag-start
handler snapshots child + frame positions; each drag tick transfers
the child's displacement to the frame while snapping the child back
into its slot, so the whole frame (and every resource in it) follows
the cursor. Framed resources stay excluded from drag-into-group.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 14:14:22 +02:00
Eduard GertandClaude Fable 5 fe5eae8625 Dragging a contained resource drags its network frame
Restore ReactFlow's fall-through: frame children are non-draggable,
so a drag on a resource moves the parent frame (and everything in
it). Clicks still open the resource editor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:53:49 +02:00
Eduard GertandClaude Fable 5 79096499c1 Stop contained resources from dragging their network frame
Dragging a non-draggable frame child fell through to dragging the
parent — contained resources now carry the "nodrag" class (clicks
still open the editor). Frame width reduced to 340px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:51:33 +02:00
Eduard GertandClaude Fable 5 c75f4d93f1 Document accepted draft-mode v1 limitations
specs/limitations.md: framed resources can't drag into groups (use the
editor's Groups field), routing-edge-only deletion leaves its router
change, network descriptions not editable on canvas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:48:49 +02:00
Eduard GertandClaude Fable 5 7d1281f941 Enlarge the draft network frame
420px wide with a taller header (64px), roomier rows and padding;
contained resources keep spanning the frame width.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:46:37 +02:00
Eduard GertandClaude Fable 5 a57f4a6276 Fixed full-width resources inside network frames; roomier nodes
- Contained resources are laid out by the frame: not draggable, and
  spanning (basically) the full frame width; popping out of a removed
  frame restores free dragging and auto width
- Network frame widened (360px) with slightly taller header/padding
  and peer-style hover treatment
- Group nodes get a little more padding

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:44:51 +02:00
Eduard GertandClaude Fable 5 b9e65b1ad6 Simplify draft resource/network node presentation
- No "Set up" affordance above incomplete resources — the dimmed
  x.x.x.x placeholder and click-to-edit carry that state
- The resource editor no longer opens on drop (click / context-menu
  Edit instead)
- Network frames: solid background with a dashed border
- Routing-peer indicator hidden entirely at 0 (the floating Add
  Routing Peer button covers it); NEW badges removed from draft
  resource and network nodes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:37:59 +02:00
Eduard GertandClaude Fable 5 2f86fd0421 Draft resources: peer-style cards inside auto-created network frames
- Dropping a resource auto-creates a draft network FRAME around it —
  a bordered, slightly opaque container node; the resource lives
  inside as a ReactFlow child (frame grows per member, children pop
  out at absolute positions when the frame is removed)
- Draft networks (panel template + editor inline-create) use the
  frame rendering: header with name/NEW badge/routing traffic light,
  "No resources yet" hint, floating Add Routing Peer button
- The draft resource node mirrors the placeholder-peer card: derived
  type icon box, NEW badge, dimmed "x.x.x.x" address placeholder;
  left-click and context-menu Edit open the resource editor
- Resources carry a single LEFT connect handle — dragging into a
  policy sets it as the destination (source side rejected); network
  assignment reparents into frames instead of membership edges
- Auto-arrange is parent-aware: child connectivity resolves onto the
  frame, children keep their relative positions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 13:26:08 +02:00
Eduard GertandClaude Fable 5 15475d8b14 Add unit tests for draft networks, resources & routing peers
- draft-connect: peer/group→network router connects, resource→network
  membership, network/resource source rejection, policy-side resource
  rules, draft-resource prefill with derived types
- changeset: create-network/resource/router recording, fold-into-create,
  network removal cascade, group-rename following into resource/router
  refs, placeholder router id renames, membership cleanup
- capabilities: routing-peer/assignable/configurable/renamable matrix
  rows; complete vs incomplete draft-resource groupability
- helpers: address→type derivation, isCompleteDraftResource, policy
  gating on tracked draft resources
- storage: round-trip of the three new change types

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:36:34 +02:00
Eduard GertandClaude Fable 5 e89d7251e2 Implement draft-mode networks, resources & routing peers
Implements specs/draft-networks-resources.md (v1):

- Networks drop with a create-network change (unique auto-name,
  deployable immediately); NetworkNode is draft-aware — NEW badge,
  draft resource count/previews, routing-peer traffic light from
  routing edges, floating "Add Routing Peer" button at 0 routers,
  Rename / Add Routing Peer / Remove context menu (removal cascades
  to dependent resource/router changes)
- Resources drop + open the new DraftResourceEditorModal (unique
  name, validated address, network selector with API + draft
  networks and inline create, optional groups/description);
  incomplete resources carry an amber "Set up" affordance and stay
  out of the changeset; completing one re-runs referencing policies
- Routing peers: peer/group → network connects (and the Add Routing
  Peer install flow) draw "routes" edges and record create-router;
  placeholder routers gate until install, then the upgrade sweep
  records them with the real peer id
- Resources are destinations-only (hard-rejected as connect
  sources); draft resources participate in policies with new-… ids
  via additionalResources, join groups (deploy fans membership out
  through the resource's own groups field)
- Deploy: groups → networks → resources → routers → policies with
  networkClientToId / resourceClientToId resolution and live-modal
  router defaults; Review & Deploy shows non-blocking warnings for
  router-less networks and policy-less resources

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:36:34 +02:00
Eduard GertandClaude Fable 5 9a7b313a19 Add Vitest unit tests for draft mode logic
npm run test:unit — Vitest + jsdom, no infra (e2e stays on Playwright).

- draft-connect.test.ts: full connect matrix (modal prefills incl.
  placeholders and XOR-side rules; direct policy-side edits with
  handle mapping, duplicate/occupied no-ops)
- node-capabilities.test.ts: per-node-kind capability spec (rename /
  install / peer-select / join-a-group for every node type)
- DraftChangesetContext.test.tsx: coalescing rules via renderHook
- helpers.test.ts: isDeployablePolicy, policy regrouping, IP/hostname
  placeholders
- draft-storage.test.ts: persistence round-trips
- Legacy hand-rolled ip/version test scripts excluded from vitest

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:07:36 +02:00
Eduard GertandClaude Fable 5 db261b184a User Device select node, upgrade follow-through, and draft consistency fixes
User Device placeholder:
- Drops a canvas node like Server/Agent, rendered as a live-style
  peer-select dropdown ("Select user device..."); choosing a peer
  upgrades the node in place and keeps the dropdown so the selection
  can be switched — policies and edges follow the new peer
- Install buttons float top-left above all placeholder nodes (inside
  the node so they zoom with the canvas), secondary styling
- Placeholder ids renamed new-<uuid> → draft-<uuid>

Group membership follow-through:
- Grouping/dragging placeholders records them as members with their
  draft ids; on install/select the id is renamed across the changeset
  (replacePeerIdInGroups) and group nodes' addedMembers; deploy
  filters never-installed draft ids
- Group counts/addedMembers update on every canvas instance of a
  group (source node + destination copies)
- Policies referencing a grouped or removed peer/resource are rewired
  (group takes the side) or cleared (side emptied) — an incomplete
  policy drops its pending create/update so broken state never deploys

Other:
- Peer view → draft: the picked peer is skipped when a canvas group
  already contains it (was overlapping the group node)
- Auto-arrange vertical spacing increased (160/80/160)
- Connect rules extracted to utils/draft-connect.ts and capability
  predicates to utils/node-capabilities.ts (pure, unit-testable),
  wired into NodeContextMenu + useDragToGroup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:07:36 +02:00
Eduard GertandClaude Fable 5 ca6ac21d52 Add spec for draft-mode networks, resources & routing peers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:01:15 +02:00
Misha Bragin b9569f48d5 Add clusters view to agent network (#714) 2026-07-15 23:22:05 +02:00
Eduard GertandClaude Fable 5 f27f7b189b Draft peers, blank policies, and placeholder install flow
Components panel & context menus:
- Panel not-found state matches the global search modal styling
- Context menus (canvas + node) flip away from viewport edges
- User Device template delays panel close so its modal opens first
- Template icon boxes match existing entities (solid bg)

Placeholder peers (Server / Agent):
- Node mirrors the real peer card (layout, text sizes, truncation);
  IP slot shows a dimmed placeholder from the account network range
- Unique per-drop names ("Agent", "Agent (1)", ...), renamable via
  context menu with uniqueness check across draft peers
- Selectable in policy modals (additionalPeers in PeerGroupSelector)
  and connectable like real peers via their draft-<uuid> ids
- Install modal suggests a unique hostname (netbird up --hostname),
  stamped on the node; useDraftPeerUpgrade watches the peers list and
  swaps installed placeholders for real peers in place, re-recording
  their policies into the changeset

Policies in draft:
- Policy template drops a blank policy node (no modal) named
  "Policy" / "Policy (1)"; blank/undeployable policies stay out of
  the changeset until they have both sides and no placeholder refs
- Peers can be dragged onto policy sides (single-peer XOR groups)
- Connect prefills the create modal (peers as peer resource, groups
  as lists) and resets stale prefill state on every connect
- Peer selector dropdown opens on the tab of the current selection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 17:34:01 +02:00
Viktor Liu 9694bb753d Show RDP for Linux and FreeBSD peers and harden the browser RDP client (#710) 2026-07-15 12:29:54 +02:00
Eduard Gert dee5c6e244 Use SmartEdge for policy edges in live views
Migrate peer/group/user/network view policy edges from the DirectionIn
edge type to SmartEdge, enabling bidirectional rendering and dynamic
side selection in live mode. DirectionIn and BidirectionalEdges remain
registered but unused.
2026-07-15 11:58:00 +02:00
Eduard Gert 6db980fcff Add draft persistence, undo/redo, and review & deploy flow
- Persist draft changes and canvas to localStorage, restored on mount
- Snapshot-based undo/redo for canvas and changeset (DraftHistoryContext)
- Review & deploy modal with grouped changes and deploy changeset hook
- Discard draft flow and leave guard for unsaved drafts
- Group rename modal and draft node creation hooks
- Move components panel into draft/ and adjust related UI
2026-07-15 11:45:32 +02:00
Eduard GertandClaude Fable 5 6a299aa594 Hide network selector in header when no networks exist
In live mode on the networks tab, the header network selector now only
renders when there is at least one network, matching the empty state
shown by ControlCenterEmptyStates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 11:29:27 +02:00
Eduard Gert 050d28d7af adjust empty draft state 2026-07-14 11:59:34 +02:00
Eduard Gert 6abf5d21c1 adjust empty draft state 2026-07-14 11:59:30 +02:00
Maycon Santos 377d239a01 Make Agent Network menu available via dashboard_features (#709)
* Make Agent Network menu available via dashboard_features.agent_network

Wire the new dashboard_features account setting into the dashboard at the API
level: add it to the Account settings interface (so settings updates round-trip
it) and have useAgentNetworkMode treat dashboard_features.agent_network === true
as enabling the Agent Network menu alongside the full dashboard. Unlike
agent_network_only it does not focus the dashboard, and it does not trigger the
onboarding flow. No settings-menu UI yet; the field is set via the API.

* Add e2e coverage for dashboard_features.agent_network menu availability

* Set dashboard_features for existing netbird.ai accounts

The signup-source flow only handled new accounts (agent_network_only) and
discarded the source for existing accounts, so nothing set dashboard_features.
Branch on account existence: new accounts get the focused view, existing
accounts get the Agent Network menu made available via
dashboard_features.agent_network. Adds e2e coverage for the existing-account
write.

* Also enable Agent Network menu for new netbird.ai accounts

New accounts got agent_network_only (focused) but not
dashboard_features.agent_network, so turning the focused view off later would
remove the Agent Network menu entirely. Set both on new accounts so disabling
focus degrades to menu-available rather than losing access.

* Match badged nav labels by substring in dashboard_features e2e

* Treat agent network deployment config as a floor over account setting

* Add agent network env x signup-source e2e matrix with a config override
2026-07-13 17:17:36 +02:00
Eduard Gert 368cfac0fe adjust sidebar 2026-07-13 17:03:09 +02:00
Eduard Gert 5c220e5176 adjust sidebar 2026-07-13 13:45:03 +02:00
Maycon Santos a266fff89d Focus dashboard on Agent Network for netbird.ai signups (#708)
Accounts that sign up through netbird.ai are focused on the Agent Network
  surface and taken through the Agent Network onboarding. Reversible by the user
  and a no-op for every other account.
  
  - Capture ?signup_source=netbird.ai pre-auth (survives the OIDC redirect); once
    authenticated, mark new accounts with settings.agent_network_only, awaiting
    /accounts revalidation before clearing the source so the view never flips.
  - useAgentNetworkMode() resolves the mode with nullish semantics: an explicit
    account setting (true or false) wins over deployment config; when absent, a
    pending netbird.ai signup or NETBIRD_AGENT_NETWORK_ONLY/_ENABLED decides. 
    Purely additive — unchanged behavior when the setting is absent. 
  - Navigation, the agent-network route guard, control-center, and fetch gates
    read the resolver instead of the config flags.
  - Onboarding: the form is chosen from signup_source synchronously, so netbird.ai
    arrivals see the Agent Network signup form (now shown on cloud) and never the
    regular form, even under a slow backend.
  - Client Settings gains an "Agent Network focused view" opt-out toggle.
  - E2E coverage for focused-view menu/routes and onboarding form selection.
2026-07-12 21:16:05 +02:00
Maycon Santos 8ed69f21c5 [reverse proxy] Cluster one click deploy (#707) 2026-07-12 15:05:02 +02:00
Eduard Gert 58d1035b16 Remove experimental flag from lazy connections (#706) 2026-07-10 14:29:18 +02:00
Pascal Fischer 991234785c new traffic filters (#704) 2026-07-09 14:26:40 +02:00
Eduard Gert 9f5e0edd45 adjust save and apply 2026-07-09 09:45:01 +02:00
Eduard Gert 9a99c5930f adjust draft mode ui 2026-07-08 17:26:24 +02:00
Eduard Gert f2ad889fdc Revert using tenants own currency and instead use MSP currency (#703) 2026-07-08 10:47:17 +02:00
Jack CarterandEduard Gert 1f219c4ffc Default billing to EUR, preserve USD for active subscribers (#702)
* feat: default billing to EUR only, preserve USD for active subscribers

Removes the USD/EUR currency selector from Plans & Billing. New
subscriptions, MSP tenants, and distributor customers now default to
EUR; any existing active USD subscription keeps its real currency so
its price stays accurate.

Also fixes a latent bug where MSP/distributor per-tenant pricing was
computed off the admin's own account currency instead of each
tenant's, pins AWS Marketplace checkout to USD since it's a separate
billing rail, and removes an unused dead TotalCost stub from the MSP
Tenants table.

* move currency helper into provider

* force usd for aws

* fix flaky setup key test

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-07-07 16:45:31 +02:00
Viktor Liu 87b3a08adc Surface readable RDP errors, keep client connected on retry, bump wasm to v0.74.2 (#701) 2026-07-07 14:11:59 +02:00
Maycon SantosandClaude Opus 4.8 cfd0037cef Show management API request id in error notifications (#698)
* Show management API request id in error notifications

Newer management servers return an X-Request-Id header on every response,
including errors. Surface it in the error toast so it can be quoted in
support tickets.

- api.tsx: read X-Request-Id from the response and attach it to the
  rejected ErrorResponse (both the JSON-body and status-text paths).
- Notification.tsx: render a copyable "Request ID" line whenever an error
  carries one, covering the promise-based mutation notifications.
- ErrorBoundary.tsx: thread the request id through for GET/fetch errors.

The line is only shown when the header is present, so older servers that
do not send it are unaffected. Adds an e2e spec covering both cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8

* docs: add request-id error toast screenshots for PR

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8

* test: expose X-Request-Id via CORS in e2e mock

The dashboard reads the request id from a cross-origin response, so the
browser only surfaces the header to JS when the response lists it in
Access-Control-Expose-Headers. Mirror a CORS-enabled management server in
the mock so the test exercises the dashboard logic rather than the header
being filtered out cross-origin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016B2yLBeDK3kvX2FvhqcWk8

* test: assert error toast via data-testid instead of text

Add a notification-title testid to the toast and target it with
getByTestId in the request-id spec, following the repo convention of
selecting by data-testid rather than brittle visible text.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 14:02:16 +02:00
Eduard Gert 1c10d195e7 add blue halo when selecting node 2026-07-07 12:19:18 +02:00
Pascal Fischer ac7d578bd7 aggregated traffic events (#693) 2026-07-07 11:36:25 +02:00
Eduard GertandClaude Opus 4.7 a57da47f45 Merge control-center-edit: draft mode, canvas toolbar, connect handles
Overlays feature/control-center-edit's control-center paths onto main:
new draft/ mode with canvas toolbar, keyboard shortcuts, drag-to-group,
create-group modal, connect-handles for drawing policies, SmartEdge
routing, and refactored ControlCenterContext into CanvasState + UI +
Policy providers. Adds hooks/views split (peer/group/user/network).

AccessControlModal + useAccessControl gain initialSourceGroups /
initialSourceResource so draft-mode policy creation can seed the source
side (previously only destination could be pre-filled).

Unrelated cloud-repo changes (data-testid -> data-cy renames, DNS,
reverse-proxy, integrations, etc.) are excluded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-06 17:10:19 +02:00
Misha Bragin 8d41de995c Fix Skip TLS doc link and model pricing format (#699) 2026-07-03 07:03:42 +02:00
Misha Bragin 30dd75d95d Feature/add skip tls verification (#697) 2026-07-01 22:25:05 +02:00
Zoltan Papp bfa064a19d Add Metrics settings tab with metrics push toggle (#613)
* Add Metrics settings tab with metrics push toggle

Add a dedicated Metrics tab to Settings with a toggle for enabling
anonymous performance metrics collection. Moves metrics out of the
Clients tab into its own tab with description and docs link.

* Update docs link
2026-07-01 19:00:00 +02:00
Misha Bragin db73d18370 Feature/add provider session (#695) 2026-07-01 16:32:44 +02:00
Misha Bragin ba32178425 Fix peer table page reset when switching kind (#696) 2026-07-01 16:09:15 +02:00
4ea4fd4a4c Reload nginx after patching CSP header (#687)
* fix(docker): reload nginx after patching CSP header

nginx is started by supervisord (priority 100) before init_react_envs.sh
(priority 201) and is never reloaded afterwards, so it keeps serving the
static default.conf CSP that has no connect-src. Over plain HTTP this blocks
OIDC discovery and the management API, breaking browser SSH/RDP auth.

Reload nginx at the end of the init script so the dynamically built CSP
header takes effect.

* Update docker/init_react_envs.sh

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(docker): log nginx -t output on failed CSP reload

When the reload never succeeds, run nginx -t before exiting so the logs show
whether the patched config is invalid or nginx never came up.

* conditionally emit `upgrade-insecure-requests` in CSP

---------

Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
2026-07-01 15:32:22 +02:00
Bethuel Mmbaga deaae9159c Show reason for peer login expiration events (#694) 2026-07-01 12:32:11 +03:00
Bethuel Mmbaga d7fd24244d Prevent gated useFetchApi hooks from overwriting shared cache entries (#692) 2026-06-30 17:44:38 +03:00
Misha Bragin 9230b3e4e3 Add Claude provider selector and Bedrock (#691) 2026-06-30 11:50:15 +02:00
Misha Bragin 19ad8d6806 Prefill Vertex endpoint (#689) 2026-06-29 10:53:07 +02:00
Misha Bragin 38fc2970e6 Support Vertex keyfile upload and agent config (#688) 2026-06-28 22:34:54 +02:00
Misha Bragin 4ec4d29e84 The empty-state peer card now renders AddPeerDropdown 2026-06-28 12:12:11 +02:00
Misha Bragin 944d1b5bf6 Agent network (#684) 2026-06-27 19:11:41 +02:00
Max Bruch c325c170ab Add origin URL of AUTH_AUTHORITY to CSP_DOMAINS #679 (#680) 2026-06-26 13:11:52 +02:00
Bethuel Mmbaga 76529fc089 Preserve inactivity expiration on partial peer updates (#676) 2026-06-23 17:30:06 +03:00
Maycon Santos 8c22e64d02 Restrict cloud/licensed-only API calls in open-source mode (#675)
Gate premium endpoints so the open-source dashboard stops calling
endpoints the open-source management server does not serve:

- MSP/Distributor: only fetch /integrations/msp, /integrations/msp/switcher
  and /integrations/msp/reseller on NetBird Cloud
- EDR bypass: only fetch /peers/edr/bypassed when licensed
- Event Streaming: gate /integrations/event-streaming GETs on the license
  in addition to the existing permission check
- useIsLicensed: cache the licensed-only probe result (per API origin, 1h
  TTL) so it no longer re-fires on every page load
- Team > Users: show the Identity Provider Sync tile in open-source mode so
  IdP sync remains discoverable
2026-06-23 15:59:15 +02:00
Maycon Santos 7653e3411c Merge NetBird cloud edition into the dashboard (#674)
Brings the unified dashboard into the open-source repo. Premium features
ship in the open code, gated at runtime via NETBIRD_CLOUD and
NETBIRD_LICENSED, with upgrade prompts for unlicensed self-hosted
deployments. Adds the cloud-only feature areas (billing, integrations,
MSP, traffic events, notifications) and the Playwright e2e suite.
2026-06-21 16:01:08 +02:00
Maycon Santos 86b6e23981 Update announcements.json (#673) 2026-06-20 16:43:04 +02:00
Eduard Gert 389a0c0785 update banner (#672) 2026-06-20 15:59:23 +02:00
Misha Bragin bf6601782d DNS Zones & Setup modal improvements (#669) 2026-06-15 16:02:33 +02:00
Max 5eac5162e0 Insert link to remote jobs documentation (#664)
Replaced general link to docs homepage with exact Documentation link for Remote Jobs
2026-06-12 09:39:36 +02:00
Brandon Hopkins 1d8d32dd12 Edit banner to IPv6 kh link (#662) 2026-06-05 12:54:59 -07:00
Brandon Hopkins 578d890bb5 Add Logout URL to the Identity Provider dialog (#657)
* Add Logout URL section

* Condensed the URL section, added logout note
2026-06-05 08:28:32 -07:00
Maycon Santos e8f0f20455 Update reverse proxy modals (#661)
* add multi-environment deployment options for reverse proxy setup

* refactor reverse proxy modal to handle state updates more robustly
2026-06-04 20:43:45 +02:00
Misha Bragin 8c94090e3d Group Networks and Routes under Network Routing (#660) 2026-06-04 19:44:13 +02:00
Misha Bragin 1917df6f60 Improve Table Filters Layout (#654) 2026-06-04 18:24:53 +02:00
Misha Bragin 358b477ded Move setup keys to Settings (#653) 2026-05-27 17:17:04 +02:00
Maycon Santosandcoderabbitai[bot] f535fe2667 Feature/private service expose (#646)
* feat: private service expose

Dashboard surface for the netbird private-service feature: the
reverse-proxy modal gains a Private toggle and the target modal gains
a Direct-upstream option with custom upstream host, both feeding the
backend's Service.private + target.direct_upstream / target.host
fields. The Proxy Events page wraps its table in PeersProvider so
peer-name fallback resolution works for tunnel-peer callers.

Reverse-proxy modal changes (ReverseProxyModal.tsx):
- Private toggle that pivots the modal between standalone and cluster
  target types and auto-injects the cluster target.

Reverse-proxy target modal changes:
- Direct upstream toggle (target dials via the host stack instead of
  the embedded NetBird client).
- Custom upstream host input revealed when Direct upstream is on.
- New ReverseProxyClusterTargetSelector + ReverseProxyAddressInput.
- useReverseProxyTargetOptions updated for the new option shape.

Proxy Events table:
- Reuses across the reverse-proxy events surface; UserCell falls back
  to the peer name when no user is attached to the call.

* feat(reverse-proxy): move NetBird-only access to auth tab + access groups

Restructures the Private (NetBird-only) flow so the auth model is
clearer:

- Removes the 'Private (NetBird-only)' toggle from the Service main
  page. Service mode is the only primary choice now.
- Adds a 'NetBird-only access' toggle on the Authentication tab, gated
  on serviceMode=HTTP and selectedDomain.supports_private===true. When
  the cluster doesn't support it, the toggle is disabled with an
  inline note explaining why.
- Adds an Access Groups picker (PeerGroupSelector) inline on the auth
  tab when NetBird-only is on. Replaces the previous bearer-auth
  distribution_groups overload — these groups go on the new
  service.access_groups field on the wire.
- When NetBird-only is enabled, all other auth modes (SSO, password,
  PIN, headers, link) are hidden — the inbound peer's WireGuard
  identity is the only auth path.
- Adds a 'Direct upstream' toggle to the Advanced settings tab,
  gated on isPrivate + cluster supports private. The toggle is
  service-level in the UI; on save it patches the (single) cluster
  target's options.direct_upstream.
- togglePrivate now also clears bearer/password/pin/header/link
  state when entering private mode (strict mutual exclusivity).

* fix(reverse-proxy): tighten NetBird-only flow per review

Five fixes from the first cut:

1. Auth-tab design mismatch. The NetBird-only block was an inline
   FancyToggleSwitch + inline PeerGroupSelector. It now follows the
   same SettingCard.Item pattern as SSO/Password/PIN/Headers: a
   clickable row showing enabled state that opens a dedicated modal
   (AuthNetBirdOnlyModal) where the access groups are picked.

2. Trimmed wordy NetBird-only description down to one line.

3. Trimmed wordy Direct upstream help text.

4. Removed the per-target Direct upstream toggle from the target
   modal. Direct upstream now lives only at the service level (under
   Advanced settings) for private services. Cluster targets still
   imply direct_upstream via the existing sanitizeTargets path, so
   the wire stays correct.

5. togglePrivate no longer drops the user's existing targets when
   entering private mode. The previous behavior silently dropped any
   non-cluster targets the operator had configured, leaving the
   targets list empty and the Save button disabled. Now: targets
   stay put, canSaveService gates on 'all targets are cluster type'
   so the Save button accurately reflects what the backend will
   accept, and an inline warning explains what to fix when the
   constraint isn't met.

The AuthNetBirdOnlyModal requires at least one access group before
Enable becomes clickable, mirroring the SSO modal's pattern.

* fix(reverse-proxy): allow any target type on private services

The cluster-target restriction was a holdover from the previous
auth-by-bearer-groups model where only cluster targets exposed a
proxy peer that could host the ACL. With the new access_groups path
the ACL is server-side and works regardless of how the proxy reaches
the upstream.

- Drop the inline 'Private services only support cluster targets'
  warning.
- canSaveService no longer requires all targets to be cluster type.
- Service-level Direct upstream now applies to every target (cluster,
  peer, host, domain) when private, so the operator can mix target
  types and still control the dial path globally.
- Tightened NetBird-only description to '...connected peers in the
  selected NetBird groups.' (per review).
- Tightened Direct upstream help to '...reachable without a Wireguard
  connection.' (per review).

A future iteration may add a dedicated cluster-only mode with its own
guided flow; for now the operator picks whatever target types suit
their topology.

* fix(reverse-proxy): count NetBird-only access as protection

The 'No Protection Configured' popup fired even on private services
because isUnprotected only checked the password/PIN/bearer/header/link
auth modes. NetBird-only access is also a form of protection (tunnel
identity + access groups), so include isPrivate in the gate.

* fix(reverse-proxy): show NetBird-only auth and resolve access group names

Two display bugs for private services:

- ReverseProxyModal seeded accessGroups with {id}-only stubs, which
  made useGroupHelper skip its ID→Group resolution and render empty
  pills in the NetBird-only modal when editing an existing service.
  Pass the raw string[] so useGroupHelper resolves full Group objects
  with names against the GroupsProvider.

- ReverseProxyAuthCell only inspected password/pin/bearer/headers
  auth flags, so a NetBird-only service displayed "No Auth" in the
  services table. Add a NetBird-only entry that counts toward the
  auth badge, renders as "NetBird Only" with the CircleUser icon
  when it's the single auth, and lists the access groups (name +
  user count) in the hover.

* feat(reverse-proxy): unify proxy-cluster target into the peer/resource selector

Operators picking a target for a service now see Proxy Clusters as a
third tab alongside Peers and Resources, gated on at least one cluster
advertising supports_private. When the service already has a proxy
cluster the tab lists only that cluster; when it doesn't, picking a
cluster commits it as the service's domain. The dedicated private-mode
cluster picker is removed in favour of this unified flow.

Selecting a cluster target now also auto-flips the service to
NetBird-only, since cluster targets are only reachable over the
WireGuard overlay and SSO/password/PIN advertise an auth path no
public client could exercise. The Access Control tab carries a note
explaining that an allow rule for the account's NetBird network range
is applied automatically alongside any operator-configured rules.

PeerGroupSelector gains opt-in showClusters/clusters/selectedCluster/
onClusterChange props plus a Proxy Clusters tab in its TabsList; the
tabOrder union widens to include "clusters". All call sites that don't
pass showClusters render unchanged.

* fix(reverse-proxy): polish private-service copy and lock Direct Upstream for cluster targets

Title-case NetBird-Only Access and Direct Upstream so the labels read
consistently across the auth tab, the dedicated modal, and the
services-table auth cell.

Cluster targets are reached via the embedded proxy's host network
stack and have no WireGuard endpoint to fall back to, so toggling
Direct Upstream off would silently break them. When any target is a
cluster the toggle is forced on, the FancyToggleSwitch is disabled,
and the helper text explains why. The save path writes the locked-on
value to every target's options.direct_upstream so the persisted
state matches what the UI shows.

* fix(reverse-proxy): support dynamic placeholder and restrict input to IPv4 or IPv6 when required

* fix(reverse-proxy): rename "Agent" to "Peer" in events user cell tooltip

* feat(reverse-proxy): gate NetBird-Only + Direct Upstream on cluster capability

When the selected cluster doesn't have at least one connected embedded
proxy (`netbird proxy`), the NetBird-Only Access and Direct Upstream
controls are visually disabled with a tooltip explaining why, instead of
silently no-op (NetBird-Only) or hidden (Direct Upstream).

- ReverseProxyCluster interface gains the optional `private` flag that
  the management API now exposes on /api/reverse-proxies/clusters.
- ClustersFeaturesCell renders a new "Private" badge for clusters whose
  `private === true`, matching the other capability badges.
- SettingCard.Item gains a `disabled` prop: opacity-50 + cursor-not-allowed,
  click/keyboard guarded, Add/Edit button greyed out. No visual change
  for existing usages (default false).
- ReverseProxyModal:
  - NetBird-Only Access card is now wrapped in a FullTooltip that
    activates only when the cluster lacks support; the SettingCard.Item
    is rendered with `disabled` in that case. Description text stops
    switching based on cluster state — the tooltip explains the gate.
  - Direct Upstream toggle is no longer hidden when supports_private is
    false; it stays visible and disabled (alongside the existing
    hasClusterTarget locked-on case), wrapped in a tooltip explaining
    the cluster requirement. Existing private services whose cluster
    lost the capability now show the control disabled rather than
    vanishing.

* disable button when no group were selected

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Feature/private service expose update (#651)

* feat(reverse-proxy): refine UI state and copy for NetBird-Only and Direct Upstream controls

- Default `Direct Upstream` to `false` for peer/resource targets; enhance tooltip descriptions.
- Improve conditional rendering of `NetBird-Only Access` setting with appropriate tooltips for unsupported clusters.
- Add cluster badge interaction in `PeerGroupSelector` and dynamic placeholder handling.
- Simplify `ReverseProxyTargetSelector` messaging for proxy forwarding options.
- Enhance `ClustersFeaturesCell` with richer description for the "Private" badge.

* refactor(reverse-proxy): improve tooltip and modal text for clarity

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-26 21:37:11 +02:00
Misha Bragin 52f7020a0f Fix broken SSH and RDP (#650) 2026-05-26 20:14:36 +02:00
Misha Bragin a604643f9a Feature/simplified layout (#649)
Splits the Peers sidebar entry into User Devices (/peers/users) and Servers (/peers/servers), with /peers redirecting to User Devices and a shared kind filter splitting peers
by whether the owner is a real user vs a service/no-user. The Servers page description and an inline link replace what was the old "Setup Keys" sidebar item under Peers.
2026-05-25 13:09:30 +02:00
Maycon SantosandEduard Gert 42cd088c5d rebuild self-hosted page as Clusters with type features (#641)
* feat(reverse-proxy): rebuild self-hosted page as Clusters with type + features

The Self-Hosted Proxies page was account-only by design but the
underlying API already returned every cluster the account could see.
Lifting that filter and renaming the page surfaces shared clusters
too — operators can see what NetBird-deployed clusters are reachable
alongside their own self-hosted ones, with online status and feature
support visible per row.

ReverseProxyCluster matches the new backend shape: `type`
(account/shared), `online`, and the three capability flags. The
`isSelfHostedCluster` provider hook now compares against `type ===
account` instead of a deprecated boolean.

Page folder renamed self-hosted-proxies → clusters (history-preserving
git mv). Table columns: Cluster (with an EphemeralPeerIndicator-style
icon next to the name marking account vs shared and a colored dot for
online status), Connected Proxies (plain numeric), Features (one
tooltip-backed badge per supported capability), Actions (Delete only
on account-owned rows; shared clusters render an empty action cell).

Empty state shows when the list is fully empty with a doc link in the
page header. Sidebar entry restored under Reverse Proxy.

* Update record in modal, update doc link, update modal title

* update reverse proxy documentation links to latest anchors

* update cluster modal description to "proxy cluster" instead of "self-hosted cluster"

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-05-20 11:48:38 +02:00
Maycon Santos 7400ac806e remove self-hosted proxies menu item (#640) 2026-05-14 17:50:07 +02:00
Viktor Liu 240ff5af9a Fix IPv6 input across reverse proxy, routes and resources (#638) 2026-05-14 16:43:02 +02:00
Eduard Gert dc86c30463 Add self-hosted proxies (#636)
* Add self-hosted proxies

* fix selfhosted badge for domain
2026-05-12 15:22:12 +02:00
Nicolas Frati e58f75ae3c Enable MFA for local users toggle (#615)
* implement enable mfa for local users toggle

* fix visibility check

* Added beta badge to MFA auth toggle
2026-05-08 16:51:17 +02:00
Viktor Liu dc1adebd27 Add IPv6 overlay settings and peer display (#594) 2026-05-07 15:20:12 +02:00
Bethuel Mmbaga d76cbd1122 Add Microsoft AD FS support for embedded Dex identity providers (#625) 2026-04-28 12:42:48 +03:00
Eduard Gert 01330e0f58 Fix missing peer context in group network routes tab (#620) 2026-04-23 17:05:05 +02:00
Viktor Liu e9ac1a1a23 Add CrowdSec IP reputation (#600) 2026-04-21 12:29:37 +02:00
raghvendra b53802a5c5 fix: prevent storage clear and logout on failed account deletion (#611) 2026-04-13 09:07:14 +02:00
Eduard Gert 9addc18956 Fix reverse proxy mode selection (#606)
* Fix reverse proxy mode selection

* Fix isNetBirdHosted

* Fix activity description
2026-04-09 09:52:35 +02:00
shuuri-labs 9701e6503b Add new pull request template + enforce documentation acknowledgement… (#602)
* Add new pull request template + enforce documentation acknowledgement in new workflow

* fix docs-ack workflow: pass PR number via env and simplify checkbox validation
2026-04-02 21:39:38 +02:00
Eduard Gert 0841caecbb Fix dns zone domain validation and peers last seen sort (#595) 2026-03-25 17:50:22 +01:00
Eduard Gert c7846760d1 Add reverse proxy auth headers (#593)
* Add reverse proxy access rules

* Fix coderabbit comments

* Fix coderabbit comments

* Fix coderabbit comments

* Add auth header modal

* Remove password managers from auth headers

* fix unique id

* Remove gradient, fix button roundness

* update lucide, add additional event auth methods

* Clear existing header value on change
2026-03-25 14:31:36 +01:00
Viktor Liu 8c283b6ef9 Support optional subdomain for reverse proxy domains (#589) 2026-03-24 16:01:01 +01:00
Eduard Gert 34ae3b4da6 Add reverse proxy access rules (#592)
* Add reverse proxy access rules

* Fix coderabbit comments

* Fix coderabbit comments

* Fix coderabbit comments
2026-03-24 16:00:31 +01:00
Viktor LiuandEduard Gert aff2365ef7 Add layer 4 protocol support to reverse proxy (#579)
* Add layer 4 proto support

* Fix initialResource fallback and UDP session_idle_timeout

* Fix tlsResourceId init for resource-driven create flows, UDP timeout label

* Address PR review: ServiceMode enum, resource init fix, modal title, a11y

* Add L4 protocol values to ReverseProxyTargetProtocol, remove unsafe double cast

* Add aria-labels to L4 port/host inputs

* Unify domain input for all service modes including L4

* Support L4 proxy events

* Fix custom port reset on edit and show port in L4 service link

* Remove redundant listen port from L4 target cell

* Show link only for HTTP/TLS services, copy-on-click for TCP/UDP

* Move mode badge before domain and use fixed width for alignment

* Fix HTTP services to open as link instead of copy

* Hide old proxy clusters from L4 domain selector

* Move service type inside modal

* Update auth cell

* Add target selector component

* Extract into separate components

* hide services types for not supported clusters

* Remove advanced settings tab in http targetmodal and use accordion instead

* Update advanced settings

* Update target device row

* Update text

* Add type cell

* Fix flat target name cell

* Update modal title

* Fix edit target in flat table

* Remove unused proxycluster interface

* Move proxy type icon into type component

* sync cloud

* use emptyrow

* fix l4 type

* fix duplicate error notification

* Set the correct target type

* Fix subnet host editable

* Fix subnet host editable

* hide selector when initial resource or peer

* Rename dropdown

* Update text

* update status cell

* merge cloud

* Update tooltips

* Address coderabbit comments

* Fix skeleton device card

* Update listen port tooltip

* Adjust padding

* update package-lock.json

* bump next to 16.1.7

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-03-18 17:43:00 +01:00
Zoltan PappandEduard Gert bad057d415 [dashboard] feat: add auto_update_always toggle to client settings (#580)
* [dashboard] feat: add auto_update_always toggle to client settings

Add "Always Update" toggle to the Clients settings tab that controls
whether updates are installed automatically in the background or require
user interaction from the UI. Includes a warning icon and caution callout
when enabled to highlight the risk of disrupting active connections.

* [dashboard] fix: improve auto-update UI clarity and toggle label

Clarify that automatic updates require user interaction by updating the
description. Rename "Always Update" to "Force Automatic Updates" for
clarity. Move warning callout below the toggle switch instead of inside it.

* Update src/modules/settings/ClientSettingsTab.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-03-16 15:35:44 +01:00
Misha Bragin 4d846e2c94 Improve text for optional resource setiings (#584) 2026-03-12 20:48:09 +01:00
Eduard Gert 15fb6e0b05 Refactor resource modal (#582) 2026-03-12 16:30:51 +01:00
Eduard Gert 55c5525626 Fix resource group policy when adding single resource as destination (#581) 2026-03-11 19:23:59 +01:00
Eduard Gert c0c1f4688e Add proxy events sort (#560)
* Add proxy events sort

* Fix coderabbit comment

* Disable local sort when server pagination is used
2026-03-10 10:10:53 +01:00
Eduard Gert b5a8f751ba Create policies inside resources (#568)
* Add acl tooltips

* Adjust resource modal and add tooltips

* Prevent nextjs navigation trigger on tab change

* Update wording

* add acl into resource

* Refactor resource policies

* Add prop to hide group edit and disable redirect

* Add skeleton loader to network page

* Create policy for new resources

* Show existing policies if groups are matching

* Add confirm dialog after creating resource without policy

* Add dialog if user edits policy that is used in multiple resources

* Add callout when selecting resource groups containing policies

* Add dialog if deleting policies containing resources

* Fix stale policies and new group creation in resource modal

* Remove whitespace

* Fix sort

* Cleanup

* Address coderabbit comments

* Fix policy alignment

* Fix initial resource

* disable selector if user did not select  resource groups

* Consider current resource when editing / deleting policy

* Remove unused mutate

* Fix dot position

* Remove ask for policy

* Fix policy index

* Fix multiple resource confirm dialog on policy cell
2026-03-10 10:10:38 +01:00
Eduard Gert 10a8e7b745 Fix stale certificate issued state (#575)
* Fix stale certificate issued state

* fix coderabbit
2026-03-09 10:08:35 +01:00
Viktor Liu 60e8394010 Add per-target options to reverse proxy (#576) 2026-03-06 18:55:28 +01:00
Eduard Gert 9420214059 Bump minimatch and ajv dependencies (#572) 2026-03-02 11:32:52 +01:00
b949f60afe Feature/client service expose (#567)
* add draft

* add reverse proxy activities

* move peer expose settings into client settings tab and fix activity descriptions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* prevent false positive group report

* add docs link

* allow save when groups are added to the setting

* Add loading skeleton to client settings, update icon, use grouphelper to allow creating new groups, remove .patch

* mv expose settings from extra settings

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-02-24 14:54:58 +01:00
Eduard Gert d498e4cc25 Fix dns records pagination (#566) 2026-02-20 21:42:26 +01:00
Eduard Gert 130dc0c32c Fix group unused filter (#565) 2026-02-19 10:32:14 +01:00
Eduard Gert f5824d6ddb Allow empty groups for reverse proxy sso auth (#563) 2026-02-18 16:27:13 +01:00
Eduard Gert 829395f908 Add hover to reverse proxy auth methods (#564) 2026-02-18 13:39:19 +01:00
Eduard Gert 8eebec78b4 Preserve query params for ssh and rdp (#559) 2026-02-16 17:34:08 +01:00
raghvendra 3e01a6dafd refactor: simplify FullScreenLoading to use boolean prop instead of string union (#555) 2026-02-16 11:10:26 +01:00
Maycon Santos 1555b94043 Fix service cluster status (#556) 2026-02-16 09:23:22 +01:00
Eduard Gert 6c62127d42 Update announcement (#553) 2026-02-13 20:56:40 +01:00
Eduard Gert b71d0fde89 Add reverse proxy (#552)
* **New Features**
  * Full Reverse Proxy UI: Services, Targets, Clusters, Custom Domains (with verification) and a Proxy Events page.
  * In-app modals for service auth (SSO, password, PIN) and a new PIN input component.

* **Improvements**
  * Network & Peer pages: tabbed views (Resources, Routing Peers, Services) and improved tables, search and filters.
  * Toast stacking/visibility and global toast styling refined.
2026-02-13 18:59:16 +01:00
Misha Bragin 84c239ce30 Indicate that local user auth is disabled (#551) 2026-02-12 15:16:34 +01:00
Aaron Dewes ba66201c64 Remove architecture info tooltip for MacOS (#550)
* Remove architecture info tooltip for MacOS

Previously, this tooltip helped users determine which binary to download. Since #501, there is only one universal binary download link, so keeping the tooltip explaining how to determine the CPU architecture is unnecessary.

* fix: Remove unused imports
2026-02-12 11:21:08 +01:00
raghvendra c6341e000f docs: fix broken Auth0 quickstart link in README (#548)
* docs: fix broken Auth0 quickstart link

* docs: spell error fixes in readme

* docs: fix typo in NETBIRD_MGMT_API_ENDPOINT placeholder in readme
2026-02-09 11:41:09 +08:00
Eduard Gert 750f660bcc Update NextJS to 16.1.6 (#547)
* Update NextJS to 16.1.6

* Update Node in workflow

* Fix rabbit comments

* Fix types

* Add engines field
2026-02-02 15:34:23 +01:00
Misha Bragin ea148545e8 Disable local users when LocalAuthDisabled = true (#546) 2026-02-01 14:31:57 +01:00
Misha Bragin d2febbf27b Fix version comparison (#544) 2026-01-27 14:13:27 +01:00
Misha Bragin 615b4487ad Point to the right upgrade doc (#543) 2026-01-27 12:23:56 +01:00
Misha Bragin a7c7800916 Add invite notification count badge (#542) 2026-01-27 10:44:39 +01:00
Eduard Gert 3d51e0893e Update announcement (#538)
* Update announcement

* Fix repeated fetches
2026-01-27 09:33:43 +01:00
Misha Bragin d7d44b5817 Adjust Invites API (#541)
* Add API adjustments

* Invite_link renamed to invite_token
2026-01-26 19:25:56 +01:00
Misha Bragin f67f39b68b Local user invites (#539) 2026-01-25 21:40:49 +01:00
dependabot[bot] d2bc7a1f57 Bump lodash from 4.17.21 to 4.17.23 (#537)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-23 13:28:28 +01:00
Eduard Gert 818ba5daa4 Allow wildcard dns zone records (#536) 2026-01-20 17:32:14 +01:00
Ali AmerandEduard Gert 3a30f76629 Add Frontend Support for Peer Debug Bundle Trigger and History (#485)
* implement debug ui

* update job ui

* Add type cell, show tooltip if peer is offline, add copy to clipboard for upload key, show error reason in tooltip

* update job event description

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-01-20 17:12:33 +01:00
Misha Bragin 34dc21c89d Add password change (embedded Idp) (#535) 2026-01-20 15:00:14 +01:00
Eduard Gert 2e37703622 Update CONTRIBUTOR_LICENSE_AGREEMENT.md (#534) 2026-01-19 14:55:04 +01:00
Eduard Gert 8aec338c43 Fix dns doc link (#533) 2026-01-19 10:01:55 +01:00
Viktor Liu f4f0c240fd Bump wasm to v0.63.0 (#531) 2026-01-19 09:49:26 +01:00
Viktor Liu 04e22a3c7e Enable SSH for Windows and Android peers (#532)
* Enable SSH for Windows and Android peers, hide update badge for temporary peers

* Fix RDP to use tcp protocol instead of netbird-ssh
2026-01-19 09:49:08 +01:00
Eduard Gert 54ef076303 Fix config vars (#529) 2026-01-16 19:59:42 +01:00
Eduard Gert 92676b6c38 Add DNS zones (#528) 2026-01-16 17:33:16 +01:00
Eduard Gert 3affa8908f Redirect /setup to /peers if no setup is required (#526)
* Redirect /setup to /peers if not setup is required

* Fix bad state while redirect

* Prevent redirect to /setup if already on /setup

* Fix loading state
2026-01-08 15:01:45 +01:00
Eduard Gert 52fd984912 Add user view to control center (#525) 2026-01-07 17:53:55 +01:00
Misha BraginandEduard Gert 83e3159ee4 Configure Identity Providers in the UI (#523)
* Add user creation with password copy

* Add initial identity provider view

* Add IdP logos

* Add IdP id to user

* Add IdP logo to user obj

* Fix okta icon

* Return callback URL when creating an IdP

* Create user for self-hosted

* Clear up password from the state

* Show IdPs and create user when enabled

* Fetch IdPs only when embedded idp is enabled

* Update src/app/(dashboard)/settings/page.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* Update src/app/(dashboard)/settings/page.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* Update src/modules/settings/IdentityProvidersTab.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* Update src/modules/settings/IdentityProviderModal.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* Update src/modules/settings/IdentityProvidersTab.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* Update src/modules/settings/IdentityProviderModal.tsx

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* Rename IdentityProvider to SSOIdentityProvider

* Fix build and extract icons

* Fix initial onboarding

* Add icons

* Move name to the top

* Fix setup wizard background color

* Update instance setup ui

* Update instance setup ui

* Use input component

* Move idp label and icons

* Fix setup wizard width

* Add authentik and keycloak

* Add idp hints

* Handle idp permissions

* Consider selfhosted instances when checking if netbird is hosted

* Update redirect

* Add max retries to redirect

* Require new secret when clientid changed

* Add callback URL on the idp creation step

* Add idp activity events

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2026-01-07 14:43:30 +01:00
Eduard Gert bf81aeb02d Add fine-grained ssh policy (#522)
* Add fine-grained ssh policy

* Update version text

* Fix coderabbit comment
2025-12-30 09:27:17 +01:00
Eduard Gert b058e66e32 Add auto update setting (#519) 2025-12-29 12:38:50 +01:00
Eduard Gert 8d6b617cbd Update NextJS to 14.2.35 (#518) 2025-12-22 11:02:29 +01:00
Eduard Gert 47db655e9f Update eslint and tailwind (#515) 2025-11-27 17:38:18 +01:00
dependabot[bot] 0661cbf9f4 Bump js-yaml from 4.1.0 to 4.1.1 (#509)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 15:25:50 +01:00
Eduard Gert 240a96fa8b Add onboarding for new accounts (#514) 2025-11-27 14:49:58 +01:00
Eduard Gert 43bc069a49 Increase ssh detection timeout (#512) 2025-11-21 10:32:50 +01:00
Eduard Gert 936de0f4f3 Add ssh policy info for peers (#511) 2025-11-20 14:29:14 +01:00
Eduard Gert d81b75a946 Bump browser ssh versions for ssh rewrite (#510)
* Bump browser ssh versions for ssh rewrite

* Remove cypress temporary
2025-11-18 17:07:58 +01:00
Eduard Gert a632eeeef0 Remove dns0eu (#508) 2025-11-10 14:21:58 +01:00
Eduard Gert e2219aeea0 Add group update activity event (#504) 2025-11-10 10:50:04 +01:00
Eduard Gert 63f4c69eb4 Fix native ssh detection (#505) 2025-11-07 09:33:58 +01:00
Eduard Gert b1af256296 Add wasm client version (#503) 2025-11-06 10:59:41 +01:00
Eduard Gertandaliamerj 4027894a2e Feature/groups page (#498)
* move our group membership from the settings menu, into the Team menu

* add action to the table and new group page

* update group page and return group settings to settings menu

* new update

* fix bug

* group action: add peer to group

* group action: add user to group

* Update wording, redirect to group page after creation

* Add better table loading skeleton

* Adjust group name cell

* Update wording

* Update sort order

* Refactor

* Merge main

* Fix button height

* Fix resources table

* Adjust table loading skeleton

* Adjust table loading skeleton

* Add loading to tab triggers

* Update meta

* Update group location

* Fix rename

* Refactor group details

* Fix linked peers

* Fix group usage

* Fix incrementing peer count

* Prevent renaming to already existing group

* Fix group name click

* Update group nav

* Make group table cells clickable

* Fix breadcrumbs

* Update wording

* Add confirmation before removing users from group

* Add permissions

* Add initial group for network routes

* Add acl and routing peer groups

---------

Co-authored-by: aliamerj <aliamer19ali@gmail.com>
2025-11-05 12:08:49 +01:00
Yanis64andEduard Gert af90792595 Add multi-group support for JWT allow groups with tag system (#500)
* fix: add multi-group support for JWT allow groups with tag system

* Update src/modules/settings/GroupsTab.tsx to use the Badge component

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>

* chore(GroupsTab): import Badge components

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2025-11-03 16:09:15 +01:00
Eduard Gert 9a401733b3 Fix toggle for p2p policies (#501) 2025-10-31 13:21:23 +01:00
Eduard Gert 07b6895380 Sync SSH & RDP changes (#495) 2025-10-16 14:44:26 +02:00
Eduard Gert 9e2e38764e Add control center (#494)
* Add control center

* Update rdp doc link
2025-10-09 11:26:21 +02:00
Maycon Santos d9fb379abf Enable connect buttons (#493) 2025-10-06 16:23:00 -03:00
Eduard Gert 831673d0d6 Sync with cloud (#491)
implements a "Sync with cloud" functionality that includes various UI improvements, code refactoring, and component extractions. The changes focus on enhancing the user interface, improving code organization, and adding new features for remote access and activity tracking.

- Refactors inline components into reusable shared components
- Adds new activity tracking for group operations
- Updates remote access configuration and UI components
- Enhances styling and layout for better user experience
2025-10-03 14:37:11 +02:00
Maycon Santos bc4aac10aa Add browser client support (#490)
* Sync wasm rdp and ssh client

* sync package-lock

* remove msp ref

* add ephemeral info
2025-10-02 00:41:08 +02:00
Eduard Gert 38e14a6c64 Allow delete groups issued by jwt (#487) 2025-09-10 14:41:19 +02:00
Maycon Santos b79c6615b4 Add user approval feature (#486)
implements a user approval feature that allows administrators to manually approve new users before they can access the system. The feature adds approval workflow controls and error handling for blocked/pending users.

Adds user approval toggle in authentication settings
Implements approve/reject actions for pending users in the users table
Creates error page for blocked/pending approval scenarios
2025-09-02 15:25:30 +02:00
hakansa 5d4e491611 Add skip_auto_apply feature to exit nodes and update related components (#484) 2025-08-20 13:11:45 +02:00
Eduard Gert 9b1f920863 Update dependencies (#483) 2025-08-19 17:32:22 +02:00
Eduard Gert 7c7f0a0f10 Add network range (#482) 2025-08-13 14:18:59 +02:00
braginini 76541c701c Update LICENSE 2025-08-05 11:19:13 +02:00
Eduard Gert d2046fee21 Add resource search, unidirectional policies for all/icmp and dns0 template (#479) 2025-07-30 17:01:49 +02:00
Eduard Gert 8e2cbe1d2a Add support for port ranges (#475) 2025-06-20 10:26:53 +02:00
Eduard Gert 8a08583225 Do not redirect on same page (#471) 2025-06-05 19:30:25 +02:00
Eduard GertandViktor Liu 1defac4e34 Update wording for dns domain, macOS and Windows install steps (#470)
* Update wording for dns domain, macOS and Windows install steps

* Update src/modules/settings/NetworkSettingsTab.tsx

Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com>

---------

Co-authored-by: Viktor Liu <17948409+lixmal@users.noreply.github.com>
2025-06-05 13:16:42 +02:00
Eduard Gert fa68f98cd0 Remove permission for add peer button (#469) 2025-06-05 13:12:38 +02:00
Eduard Gert 3f6e4c4e4f Add lazy connection setting (#465) 2025-06-04 11:54:18 +02:00
Eduard GertandMaycon Santos 0e2661caea Merge cloud changes to public (#462)
* Add changes from dashboard cloud

* Add changes from dashboard cloud

* Update next.js version

* Small formatting changes

* remove unknown permission check

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2025-05-05 15:30:28 +02:00
Eduard Gert d7c5f7e183 Hide update available for mobile devices (#106) (#460)
(cherry picked from commit 7f248ae060385acb1245591bd46e2bb6d53ed908)
2025-04-28 11:58:26 +02:00
Eduard Gert ebbe865ce0 Add custom dns domain (#458)
* Update domain validator

* Add custom dns domain
2025-04-28 11:58:16 +02:00
Eduard Gert 6c0ab88488 Update domain validator (#459) 2025-04-28 11:53:44 +02:00
Eduard Gert a50576d851 Fix nameserver port input for Safari (#456) 2025-04-28 11:21:25 +02:00
Eduard Gert 676250266c Fix browse posture checks table filters (#448) 2025-04-07 10:23:07 +02:00
Vladislav Tropnikov 042c65a652 Add display of ID if user does not have email (#450)
* Add display of ID if user does not have email

* Update PeerNameCell.tsx

* Add more possible id parameters

* Hide user if there is nothing

* change id order

* Keep default behavior
2025-03-27 17:30:26 +01:00
Misha Bragin 96f2d39e54 Add CLA 2025-03-18 15:58:05 +01:00
Eduard Gert 61e11d3740 Apply recent cloud changes (#447)
* Add resource description, add single resource for acl, add icons for group badges, add inactivity expiration

* Add extra dns labels, remove routing restriction
2025-02-21 15:53:40 +01:00
Edouard VanbelleandEduard Gert c8e3b50f1b Display serial number on peer information and on peers table (#444)
* display serial number on peer information and on peers table

  * add serial on peers list (included in OS information to minimize informations)
  * permit a lookup via serial number
  * add serial on peer information

* Update os icon to match existing one and hide serial if it does not exist

---------

Co-authored-by: Eduard Gert <kontakt@eduardgert.de>
2025-01-30 13:14:38 +01:00
Eduard GertandMisha Bragin 25be69e7bb Add improvements to new networks features (#439)
* Fix wrong ui state for routing peer modal in networks

* Add confirmation dialog when blocking users

* Keep peer sort order when switching pages

* Update sidebar navigation order and remove deprecation notice

* Fix issue when hovering over truncated text in a group badge closes the multiple groups popover

* Update group text in network resource modal

* Update networks page text

* Fix line height

* Add search to resource table

* Switch networks flow to create first resources and then add routers

* Add enabled toggle to routing peers

* Add enabled toggle to network resources

* Add resource group modal and adjust tables

* Clarify networks

* Fix not properly aligned horizontal scroll bar

* Add option to install netbird after creating a setup key

* Fix text for install netbird modal

* Show resources count in group settings

* Fix "no results" and "no routing peers" text showing at the same time

* Fix wording

* Fix resource policy count

* Hide resource count when selection source groups

* Extend networks routing peer modal with option to create a setup key and install netbird

* Add option for horizontal stepper

* Generate setup key when installing netbird from routing peer modal

* Add confirm dialog to let the user know a one-off setup-key will be created. This avoids accidental clicking and later confusion on the setup keys page

---------

Co-authored-by: Misha Bragin <bangvalo@gmail.com>
2025-01-20 16:18:21 +01:00
Eduard Gert 43e5d5cf53 Fix activity search and allow searching for meta fields (#440) 2025-01-15 16:41:55 +01:00
Eduard Gert 18819d6fdf Add confirmation dialog when blocking users (#437) 2025-01-15 16:29:05 +01:00
Eduard Gert 158804c1ac Fix wrong ui state for routing peer modal in networks (#436) 2025-01-15 16:28:50 +01:00
Misha Bragin 14d2d68819 Update links to networks doc (#435) 2024-12-27 21:48:36 +01:00
Pascal Fischer 40902b3629 add resources to groups update operation (#434) 2024-12-27 14:20:11 +01:00
Pascal Fischer fa9bcea4ab Update links for networks concept (#433) 2024-12-23 18:08:06 +01:00
Eduard Gert 3ba7acdecf Add new networks feature (#427) 2024-12-23 13:20:01 +03:00
Eduard Gert c7775ade8c Hide groups for regular users (#423) 2024-11-20 17:50:28 +01:00
Maycon Santos cd3e75b640 Add setup-key improvements (#420)
- Add support to key deletion
- Add custom and unlimited expiration
2024-11-01 16:04:43 +01:00
Jon "The Nice Guy" Spriggs f8281c8057 Typo (#418)
Protocol appears to include the : delimiter
2024-10-22 11:19:16 +02:00
Eduard Gert c1fcadaefe Fix resetting acl groups on switching active toggle (#417) 2024-10-07 17:31:42 +02:00
Jon "The Nice Guy" SpriggsandEduard Gert a0c4520f4b Add admin-url to the add-peer dialogue (#416)
* Add admin-url to the add-peer dialogue

* Missed "let" from defining the variable

* Update netbird.ts

Fix isNetBirdHosted check

---------

Co-authored-by: Eduard Gert <eduard@netbird.io>
2024-10-07 17:25:03 +02:00
Eduard Gert 76ef50a886 Add Access Control Groups & various UI / UX improvements (#415)
* Update codespell

* Add access control group, add various ui / ux improvements
2024-10-04 19:54:49 +02:00
Maycon Santos 58cec8fcd1 ignore mappin spelling (#408) 2024-08-13 15:59:38 +02:00
Eduard Gert d34ae9beb2 Sync changes with netbird cloud (#407)
* Update axa oidc library and package.json

* Update ACL port state to show correct value

* Filter user groups by unique groups only

* Add peer multiselect, optimize dropdown performance for peer selection, remove 'all' group from some dropdowns, various ui / ux optimizations

* Add peer multiselect, optimize dropdown performance for peer selection, remove 'all' group from some dropdowns, various ui / ux optimizations
2024-08-13 15:51:22 +02:00
Eduard Gert 650496f670 Include all settings in put request to prevent overwrite (#405) 2024-07-31 18:48:59 +02:00
Tom Hubrecht 121778c4a6 Fix package-lock.json (#401) 2024-07-12 10:35:31 +02:00
juliaroesschen d4102c5d04 fix typo in route update modal (#397) 2024-07-04 15:05:57 +02:00
pascal-fischer e78c35bdbe Fix DNS modal to allow one char domains (#393)
* update regex to allow one char domains in DNS routing modal

* update regex
2024-07-04 10:50:37 +02:00
juliaroesschen 6ebee98695 Fix typo in Network Routes dialogue (#395) 2024-07-04 10:48:49 +02:00
juliaroesschen f4b28d5f40 Fix typo in routes modal 2024-06-28 11:38:39 +02:00
Eduard Gert b4b6d9295b Add DNS routes (#390) 2024-06-17 09:32:55 +02:00
Maycon Santos 4898742ee9 Fix http://localhost:3000/ url validation case (#388)
* Fix http://localhost:3000/ url validation case

* adjust min regex occurrences
2024-06-12 18:18:14 +02:00
Eduard Gert 79164e9dd5 Add process posture check (#378)
* Add process posture check

* Add support for separate linux and mac paths
2024-06-12 16:32:10 +02:00
Eduard Gert 5caeab118b UX changes for modals and refactoring (#380) 2024-05-08 14:42:04 +02:00
Eduard Gert 3f943bb7d4 Use next/font/local instead of next/font/google (#376) 2024-04-19 17:12:56 +02:00
Eduard Gert 96b939e6cc Add changes from cloud repo to public one (#377)
* Remove unused files

* Update activity descriptions

* Update SelectDropdown

* Update redirect logic for / page

* Update HelpText.tsx

* Update wording for exit nodes
2024-04-19 17:12:37 +02:00
Eduard Gert 5e13548b81 Add better input validation for setup-keys, nameserver and routes (#373)
* Return the correct promise for errors

* Update icon

* Add better validation for routes

* Add better validation for DNS

* Add better validation for setup keys

* Merge exit nodes to input validation
2024-04-17 15:27:21 +02:00
Eduard Gert 2272a1d2a4 Add Exit Nodes (#374)
* Add exit node feature

* Fix spelling

* Hide masquerade for exit nodes

* Add exit node information to peers list

* Change exit node button, add indicator to peers table

* Add steps to route modal

* Add hook to check if peer has exit nodes

* Hide exit node indicator for regular users

* Add documentation links
2024-04-17 13:11:38 +02:00
Eduard Gert fc3da50346 Add fallbacks for setup key name & setup key group names (#370)
* Add try catch block for global search

* Add fallback for group name

* Add fallback for setup key name

* Do not load setup key modal if it's not open

* Check if auto_groups actually exists for the setup keys

* Add fallback for group names in setup keys table

* Add fallback for group names in peers table
2024-04-11 16:42:27 +02:00
Eduard Gert 6d4716cdad Remove integrations from public repo and sync changes (#369)
* Change icon size

* Remove integrations

* Add no cache header

* Add analytics event tracking

* Add small announcement improvements

* Remove peer approval setting

* Do not load countries when user has no permission

* Add tab query params to settings

* Decrease navigation font size

* Change order of providers

* Increase padding for modals

* Show page only when user is fully loaded and found

* Remove unused state

* Remove integrations page
2024-04-02 14:06:38 +02:00
amplitudes 859916b1df fix: user deletion notification (#367) 2024-04-02 12:26:45 +02:00
Eduard Gert 80ce7d21b0 Fix issue where the first users cache is not populated (#366) 2024-03-28 11:27:00 +01:00
Eduard Gert 06fdbd8ec4 Hide profile settings and announcements for blocked dashboard view (#365) 2024-03-28 10:25:21 +01:00
Eduard Gert 973cceff79 Add setting to change dashboard view for regular users (#362) 2024-03-27 16:09:58 +01:00
Eduard Gert f4a2d6fae8 Add Okta SCIM integration (#361)
* Add Okta integration (wip)

* Update okta setup dialog

* Add okta integration images

* Add error handling for 500 status codes

* Add okta integration

* Fix lint warnings

* Update azures last sync time

* Remove 'on' from step, disable copy for HTTP Header

* Update text for custom IDP
2024-03-27 15:55:56 +01:00
Eduard Gert cb922b46b7 Add 'Offline' filter to peers table (#364) 2024-03-26 20:03:24 +01:00
Eduard Gert 4c56ae704c Show peers for regular users but hide / disable actions (delete, enable ssh etc.) (#360)
* Show peers for regular users but hide / disable actions (delete, enable ssh etc.)

* Do not load countries for regular users
2024-03-21 14:21:26 +01:00
Eduard Gert fe6d8c9bd5 Add support for decimal expiration time and switch to days if interval exceeds 48h (#357)
* Add helper function to check for integer

* Add support for decimal expiration time and switch to days if interval exceeds 48h
2024-03-15 15:54:06 +01:00
Eduard Gert 121976d101 Add option to copy peer details (ip, public ip, hostname, domain name) in detailed peer view (#356) 2024-03-15 13:46:27 +01:00
Eduard Gert f7071e00b6 Add reset filter button (#355) 2024-03-15 13:43:00 +01:00
Eduard Gert 6b73ccf102 Fix search resetting when selecting a group (#354) 2024-03-15 13:35:25 +01:00
Eduard GertandMaycon Santos 87dcd00264 Fix peer groups occasionally not refreshing (#351)
* Trigger groups refresh when visiting peers page

* Disable exhaustive-deps linter

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-03-15 13:34:47 +01:00
Eduard Gert 99f1bcc375 Reduce information visible to regular users (non-adminstrators) (#353)
reducing visibility to display only add peer information
2024-03-15 13:25:40 +01:00
Eduard Gert bf34c55110 Fix JWT group sync checkbox using wrong variable (#352) 2024-03-12 17:23:42 +01:00
Eduard Gert 1dfc6e2d75 Add announcement banner to show updates or important information (#350)
* Add contrast color

* Add crypto-js for md5 hash

* Add announcement banner
2024-03-11 15:31:52 +01:00
Eduard Gert b7860a8786 Filter peers by id instead of name in peer dropdown selector (#347) 2024-03-09 18:07:45 +01:00
Eduard Gert c9172e3a5f Show full netbird logo on desktop and netbird logomark on mobile (#348) 2024-03-09 18:07:26 +01:00
Eduard Gert 78d75134f9 Add better description for posture check activity events (#349) 2024-03-09 17:14:41 +01:00
Eduard Gert 071feb02f9 Fix SSO expiration dropdown to reflect the actual "Hours" or "Days" (#345) 2024-03-01 17:01:26 +01:00
Eduard Gert 8e7bcc0c22 Extend posture checks with peer network range check (#344)
add support to peer network checks
2024-02-27 16:15:47 +01:00
Eduard Gert 02a0b71e46 Fix setup key modal closing on first time creation (#342) 2024-02-26 18:02:56 +01:00
Eduard Gert a8b66d935f Show loading indicator for peer detail view as groups are loading (#343) 2024-02-26 18:02:28 +01:00
Eduard GertandMaycon Santos f74f9cf812 Add region and public ip to peer table and detailed peer view (#340)
* Fix group badge icon size

* Fix copy icon size

* Add region information to peer table and single peer view

* Push to docker

* Change login expired icon size

* Fix country flag in single peer view

* Change country flag size in peer table

* Disable revalidation for countries

* Fix icon size on peer detail view

* Rollback workflow

* Revert login expiration

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-02-23 15:52:33 +01:00
Maycon Santos 7578595f05 Update posture checks documentation links (#339) 2024-02-22 21:46:55 +01:00
Eduard Gert a5fc05ca3a Add posture checks to further restrict network access (#338) 2024-02-22 13:58:41 +01:00
Eduard Gert 8ffdb442f1 Allow adding 3 nameserver addresses (#337) 2024-02-19 14:29:33 +01:00
Eduard Gert a04e3afccb Show "Never" when a user never logged in instead of a date (#335) 2024-02-16 12:15:32 +01:00
Eduard Gert bca327e4cf Add better search for network-routes by group name (#336) 2024-02-16 12:15:14 +01:00
Maycon Santos 6c74506316 Add templates for bugs and for feature request (#333) 2024-02-14 13:43:27 +01:00
Eduard Gert 663d7ea58c Add check to call initial users only once in dev mode (#332) 2024-02-13 15:11:37 +01:00
Eduard Gert b701783dca Update ephemeral_peers to ephemeral (#331) 2024-02-13 14:12:31 +01:00
Eduard Gert fc9a9dfa3e Block application and show loading until users are fetched (#330)
* Add option to ignore errors

* Block application and show loading until users are fetched
2024-02-13 14:08:43 +01:00
Eduard Gert 093efc08b3 Fix an issue of creating duplicate groups in the access control and network routes modal when group does not exist (#328) 2024-02-12 14:12:57 +01:00
Eduard Gert dfa41a48e3 Hide the user invite button for selfhosted users (#327) 2024-02-12 14:08:10 +01:00
Eduard Gert 2cf366a5f8 Fix access control to show the correct modal (#326)
* Rename Access Control "Rule" to Access Control "Policy"

* Show the correct modal for Access Control
2024-02-12 14:07:53 +01:00
Eduard Gert f91788faef Fix iOS detection and modal scrolling on Safari mobile (#325)
* Add better iOS detection

* Fix scrolling for Safari browser
2024-02-12 14:07:31 +01:00
Eduard Gert ec7bb76f1e Fix closing of tab when creating setup-key (#324) 2024-02-12 14:06:59 +01:00
Eduard Gert 15bab2cef4 Merge pull request #322
* Add unique key for nameservers
2024-02-12 14:05:26 +01:00
Eduard Gert 4fa3482c74 Merge pull request #318
* Fix redirect link to event streaming docs
2024-02-12 14:04:42 +01:00
Eduard Gert f5059f485c Fix invalid token error message (#321) 2024-02-09 16:07:09 +01:00
Eduard Gert 3c60de4169 Add a fallback in case the user has no name (#320)
* Fix redirect link to event streaming docs

* Fallback to a user id in case user has no name
2024-02-05 16:48:25 +01:00
Eduard Gert 2267cecf46 Change fetch() to support idToken source (#319)
* Fix redirect link to event streaming docs

* Change fetch logic to support okta oidc
2024-02-05 11:08:36 +01:00
Eduard GertandMaycon Santos 2b222e082a Init Dashboard V2 (#316)
* Init Dashboard V2

* Update README.md

* use dedicated vars and prevent docker push on PRs

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-01-30 13:34:42 +01:00
Eduard GertandMaycon Santos 4612f6c49a Add "New dashboard" Banner (#315)
* Add new dashboard Banner.tsx

* display on localdev and add todo note

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-01-24 18:36:20 +01:00
Maycon Santos a3a0e6315f Use Approve for approve peer modal (#312) 2024-01-02 20:07:10 +01:00
Misha Bragin fa7bc205ba Add reference to the Apple Store on the Add Peer window (#311) 2023-12-29 18:03:50 +01:00
pascal-fischer 87ff65f1a7 adding peers approval flag to non user bound devices (#310) 2023-12-25 13:01:54 +01:00
Bethuel Mmbaga 748596f710 Add JWT allow groups support in account settings (#309) 2023-12-18 12:14:03 +01:00
Misha Bragin b06cb0ec3d Add a settings page for custom IdP and MFA (#308) 2023-12-12 13:42:14 +01:00
Misha Bragin 0c924f7ded Correct peer approval tooltip (#307) 2023-12-12 13:33:11 +01:00
Maycon Santos f29c915fc2 fix settings update blank page 2023-12-11 18:07:05 +01:00
Maycon Santos 5f8579bfda Fix peer expiration interval init on validation (#306) 2023-12-11 17:46:32 +01:00
2060 changed files with 231719 additions and 53729 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
# The image only needs the pre-built static export (out/) and the docker/
# config files. Keep the build context small by dropping everything heavy and
# irrelevant — without this, `docker build .` ships node_modules and .next.
node_modules
.next
.git
.github
e2e
src
public
*.tar.gz
*.tar
@@ -0,0 +1,44 @@
---
name: Bug/Issue report
about: Create a report to help us improve
title: ''
labels: ['needs-triage']
assignees: ''
---
**Describe the problem**
A clear and concise description of what the problem is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Are you using NetBird Cloud?**
Please specify whether you use NetBird Cloud or self-host NetBird's control plane.
**NetBird version**
`netbird version`
**NetBird status -d output:**
If applicable, add the `netbird status -d' command output.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ['feature-request','needs-triage']
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+19
View File
@@ -0,0 +1,19 @@
## Issue ticket number and link
## Documentation
Select exactly one:
- [ ] I added/updated documentation for this change
- [ ] Documentation is **not needed** for this change (explain why)
### Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
## E2E tests
Optional: override the image tags used by the Playwright e2e workflow.
Defaults to `main` when omitted.
management-cloud-tag: main
reverse-proxy-tag: main
+125 -23
View File
@@ -7,50 +7,152 @@ on:
- "**"
pull_request:
# Cancel in-progress runs on the same ref (PR or branch) when a new commit
# arrives, so we don't waste CI building superseded commits.
concurrency:
group: build-and-push-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DOCKERHUB_IMAGE: netbirdio/dashboard
GHCR_IMAGE: ghcr.io/netbirdio/dashboard-cloud
jobs:
build_n_push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
- name: setup-node
uses: actions/setup-node@v3
uses: actions/setup-node@v5
with:
node-version: '16'
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build
# skiping fail on warning for now
run: CI=false npm run build
-
- run: echo '{}' > .local-config.json
- name: Download IronRDP release TS files
uses: robinraju/release-downloader@v1.7
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: netbirdio/IronRDP
tag: v0.0.2
fileName: "*.ts"
out-file-path: 'public/ironrdp-pkg'
- name: Download IronRDP release JS files
uses: robinraju/release-downloader@v1.7
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: netbirdio/IronRDP
tag: v0.0.2
fileName: "*.js"
out-file-path: 'public/ironrdp-pkg'
- name: Download IronRDP release WASM file
uses: robinraju/release-downloader@v1.7
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: netbirdio/IronRDP
tag: v0.0.2
fileName: "ironrdp_web_bg.wasm"
out-file-path: 'public/ironrdp-pkg'
- name: Get version from tag
id: version
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
else
echo "version=development" >> $GITHUB_OUTPUT
fi
- name: Build
run: npm run build
env:
NEXT_PUBLIC_DASHBOARD_VERSION: ${{ steps.version.outputs.version }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: wiretrustee/dashboard
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
images: |
${{ env.DOCKERHUB_IMAGE }}
${{ env.GHCR_IMAGE }}
flavor: |
latest=false
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
type=ref,event=pr
type=sha
- name: Docker meta (rootless)
id: meta_rootless
uses: docker/metadata-action@v5
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
-
name: Docker build and push
uses: docker/build-push-action@v3
images: |
${{ env.DOCKERHUB_IMAGE }}
${{ env.GHCR_IMAGE }}
flavor: |
latest=false
suffix=-rootless,onlatest=true
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
type=ref,event=pr
type=sha
- name: Log in to Docker Hub
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@v3
with:
username: ${{ secrets.NB_DOCKER_USER }}
password: ${{ secrets.NB_DOCKER_TOKEN }}
- name: Log in to the GitHub Container registry
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
platforms: linux/amd64,linux/arm64,linux/arm
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push rootless Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile.rootless
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
platforms: linux/amd64,linux/arm64,linux/arm
tags: ${{ steps.meta_rootless.outputs.tags }}
labels: ${{ steps.meta_rootless.outputs.labels }}
- if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
run: |
echo '### Pushed tags' >> $GITHUB_STEP_SUMMARY
echo '${{ steps.meta.outputs.tags }}' >> $GITHUB_STEP_SUMMARY
echo '${{ steps.meta_rootless.outputs.tags }}' >> $GITHUB_STEP_SUMMARY
+81
View File
@@ -0,0 +1,81 @@
name: Bump netbird wasm client
on:
workflow_dispatch:
inputs:
tag:
description: 'NetBird release tag (e.g. v0.78.0)'
required: true
type: string
concurrency:
group: ${{ github.workflow }}-${{ inputs.tag }}
cancel-in-progress: false
jobs:
bump:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.NETBIRD_DEV_BOT_GITHUB_TOKEN }}
- name: Configure git
run: |
git config --global user.email "dev@netbird.io"
git config --global user.name "netbirddev"
# The tag rides in env and is validated before use, so a crafted dispatch
# input cannot expand into shell source or reach the sed expression.
- name: Update the default wasm path
id: bump
env:
TAG: ${{ inputs.tag }}
run: |
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Refusing tag '$TAG': expected vMAJOR.MINOR.PATCH"
exit 1
fi
sed -i "s|https://pkgs.netbird.io/wasm/client/v[0-9.]*|https://pkgs.netbird.io/wasm/client/${TAG}|" src/utils/config.ts
branch="bump-netbird-wasm-${TAG#v}"
echo "branch=${branch}" >> "$GITHUB_OUTPUT"
# The branch name is derived from the tag, so a re-dispatch of the
# same release would collide with the branch and pull request an
# earlier run left behind.
if git ls-remote --exit-code --heads origin "${branch}" >/dev/null 2>&1; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Branch ${branch} already exists, ${TAG} was handled by an earlier run"
exit 0
fi
git checkout -b "${branch}"
git add src/utils/config.ts
if git diff --cached --quiet; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Default wasm path already at ${TAG}, nothing to do"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
git commit -m "Bump the WASM client to ${TAG}"
git push --set-upstream origin "${branch}"
- name: Create pull request
if: steps.bump.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.NETBIRD_DEV_BOT_GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
BRANCH: ${{ steps.bump.outputs.branch }}
run: |
gh pr create \
--base main \
--head "$BRANCH" \
--title "Bump the WASM client to ${TAG}" \
--body "Automated wasm path bump triggered by netbird release ${TAG}."
+2 -1
View File
@@ -12,4 +12,5 @@ jobs:
uses: codespell-project/actions-codespell@v2
with:
only_warn: 1
skip: package-lock.json,*.svg
skip: package-lock.json,*.svg
ignore_words_list: mappin, allTime
+105
View File
@@ -0,0 +1,105 @@
name: Docs Acknowledgement
on:
pull_request:
types: [opened, edited, synchronize]
permissions:
contents: read
pull-requests: read
jobs:
docs-ack:
name: Require docs PR URL or explicit "not needed"
runs-on: ubuntu-latest
steps:
- name: Read PR body
id: body
shell: bash
run: |
set -euo pipefail
BODY_B64=$(jq -r '.pull_request.body // "" | @base64' "$GITHUB_EVENT_PATH")
{
echo "body_b64=$BODY_B64"
} >> "$GITHUB_OUTPUT"
- name: Validate checkbox selection
id: validate
shell: bash
env:
BODY_B64: ${{ steps.body.outputs.body_b64 }}
run: |
set -euo pipefail
if ! body="$(printf '%s' "$BODY_B64" | base64 -d)"; then
echo "::error::Failed to decode PR body from base64. Data may be corrupted or missing."
exit 1
fi
added_checked=$(printf '%s' "$body" | grep -Ei '^[[:space:]]*-\s*\[x\]\s*I added/updated documentation' | wc -l | tr -d '[:space:]' || true)
noneed_checked=$(printf '%s' "$body" | grep -Ei '^[[:space:]]*-\s*\[x\]\s*Documentation is \*\*not needed\*\*' | wc -l | tr -d '[:space:]' || true)
total=$((added_checked + noneed_checked))
if [ "$total" -ne 1 ]; then
echo "::error::You must check exactly one docs option in the PR template (either 'docs added' OR 'not needed')."
exit 1
fi
if [ "$added_checked" -eq 1 ]; then
echo "mode=added" >> "$GITHUB_OUTPUT"
else
echo "mode=noneed" >> "$GITHUB_OUTPUT"
fi
- name: Extract docs PR URL (when 'docs added')
if: steps.validate.outputs.mode == 'added'
id: extract
shell: bash
env:
BODY_B64: ${{ steps.body.outputs.body_b64 }}
run: |
set -euo pipefail
body="$(printf '%s' "$BODY_B64" | base64 -d)"
# Strictly require HTTPS and that it's a PR in netbirdio/docs
# e.g., https://github.com/netbirdio/docs/pull/1234
url="$(printf '%s' "$body" | grep -Eo 'https://github\.com/netbirdio/docs/pull/[0-9]+' | head -n1 || true)"
if [ -z "${url:-}" ]; then
echo "::error::You checked 'docs added' but didn't include a valid HTTPS PR link to netbirdio/docs (e.g., https://github.com/netbirdio/docs/pull/1234)."
exit 1
fi
pr_number="$(printf '%s' "$url" | sed -E 's#.*/pull/([0-9]+)$#\1#')"
{
echo "url=$url"
echo "pr_number=$pr_number"
} >> "$GITHUB_OUTPUT"
- name: Verify docs PR exists (and is open or merged)
if: steps.validate.outputs.mode == 'added'
uses: actions/github-script@v7
id: verify
env:
PR_NUMBER: ${{ steps.extract.outputs.pr_number }}
with:
script: |
const prNumber = parseInt(process.env.PR_NUMBER, 10);
const { data } = await github.rest.pulls.get({
owner: 'netbirdio',
repo: 'docs',
pull_number: prNumber
});
// Allow open or merged PRs
const ok = data.state === 'open' || data.merged === true;
core.setOutput('state', data.state);
core.setOutput('merged', String(!!data.merged));
if (!ok) {
core.setFailed(`Docs PR #${prNumber} exists but is neither open nor merged (state=${data.state}, merged=${data.merged}).`);
}
result-encoding: string
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: All good
run: echo "Documentation requirement satisfied ✅"
+206
View File
@@ -0,0 +1,206 @@
name: Playwright E2E Tests
on:
push:
branches: [main]
pull_request:
# `edited` is included so that updating the PR description (e.g. to set
# `management-cloud-tag: <tag>` or `reverse-proxy-tag: <tag>`) re-triggers
# the e2e run with the new tag.
types: [opened, synchronize, reopened, edited]
workflow_dispatch:
inputs:
management-cloud-tag:
description: 'Management Cloud image tag'
required: true
type: string
default: 'main'
reverse-proxy-tag:
description: 'Reverse Proxy image tag'
required: true
type: string
default: 'main'
env:
REGISTRY: ghcr.io
jobs:
playwright-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
fetch-depth: 0
- name: setup-node
uses: actions/setup-node@v5
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Log in to the Container registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.CI_DOCKER_PULL_GITHUB_TOKEN }}
- run: echo '{}' > .local-config.json
- name: Install jq
run: sudo apt-get install jq
- name: Resolve management-cloud image tag
id: management_tag
env:
INPUT_TAG: ${{ inputs.management-cloud-tag }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
# Use workflow_dispatch input if provided, otherwise parse PR body.
# Falls back to `main` when not specified.
if [ -n "$INPUT_TAG" ]; then
TAG="$INPUT_TAG"
else
TAG=$(printf '%s' "$PR_BODY" \
| grep -iE '^[[:space:]]*management-cloud-tag:[[:space:]]*[A-Za-z0-9._-]+[[:space:]]*$' \
| head -n1 \
| sed -E 's/^[[:space:]]*[Mm]anagement-cloud-tag:[[:space:]]*([A-Za-z0-9._-]+)[[:space:]]*$/\1/' \
|| true)
if [ -z "$TAG" ]; then
TAG="main"
fi
fi
echo "Using management-cloud tag: $TAG"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Resolve reverse-proxy image tag
id: reverse_proxy_tag
env:
INPUT_TAG: ${{ inputs.reverse-proxy-tag }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
if [ -n "$INPUT_TAG" ]; then
TAG="$INPUT_TAG"
else
TAG=$(printf '%s' "$PR_BODY" \
| grep -iE '^[[:space:]]*reverse-proxy-tag:[[:space:]]*[A-Za-z0-9._-]+[[:space:]]*$' \
| head -n1 \
| sed -E 's/^[[:space:]]*[Rr]everse-proxy-tag:[[:space:]]*([A-Za-z0-9._-]+)[[:space:]]*$/\1/' \
|| true)
if [ -z "$TAG" ]; then
TAG="main"
fi
fi
echo "Using reverse-proxy tag: $TAG"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Restore geolocation database cache
id: geo-cache
uses: actions/cache/restore@v4
with:
path: e2e/environment/geo-cache
key: geolite-dbs-v1
- name: Build dashboard and set up test environment
env:
MANAGEMENT_IMAGE_TAG: ${{ steps.management_tag.outputs.tag }}
REVERSE_PROXY_IMAGE_TAG: ${{ steps.reverse_proxy_tag.outputs.tag }}
# The docker environment (image pulls, Zitadel bootstrap) is
# network-bound and the Next build is CPU-bound, so they overlap.
# The build reads .test-config.json via the @/config/test alias,
# and create-test-env.sh renders that file only after the Zitadel
# bootstrap — so the build waits for it before starting.
run: |
(cd ./e2e/environment && bash create-test-env.sh) > env-setup.log 2>&1 &
ENV_PID=$!
until [ -s .test-config.json ] && jq empty .test-config.json 2>/dev/null; do
if ! kill -0 "$ENV_PID" 2>/dev/null; then break; fi
sleep 2
done
if ! [ -s .test-config.json ]; then
echo "::error::create-test-env.sh exited before rendering .test-config.json"
cat env-setup.log
exit 1
fi
# Lowest CPU priority: the containers must win the scheduler, or
# the build can starve management past the proxy-registration wait.
if ! nice -n 19 env APP_ENV=test npx next build; then
kill "$ENV_PID" 2>/dev/null || true
exit 1
fi
if ! wait "$ENV_PID"; then
echo "::error::Test environment setup failed"
cat env-setup.log
exit 1
fi
cat env-setup.log
# Saved right after env setup rather than in a post step, so a later
# test failure doesn't lose the cache.
- name: Save geolocation database cache
if: always() && steps.geo-cache.outputs.cache-hit != 'true' && hashFiles('e2e/environment/geo-cache/**') != ''
uses: actions/cache/save@v4
with:
path: e2e/environment/geo-cache
key: geolite-dbs-v1
- name: Run Playwright tests
id: playwright
run: |
set -o pipefail
npx playwright test --config=e2e/playwright.config.ts 2>&1 | tee playwright-output.log
- name: Append Playwright summary to job summary
if: always() && hashFiles('e2e/test-results/results.json') != ''
run: |
if [ -f e2e/test-results/results.json ]; then
passed=$(jq '.stats.expected // 0' e2e/test-results/results.json)
failed=$(jq '.stats.unexpected // 0' e2e/test-results/results.json)
skipped=$(jq '.stats.skipped // 0' e2e/test-results/results.json)
duration=$(jq '.stats.duration // 0' e2e/test-results/results.json)
{
echo '### Playwright results'
echo ''
echo "| Passed | Failed | Skipped | Duration |"
echo "|--------|--------|---------|----------|"
echo "| $passed | $failed | $skipped | ${duration}ms |"
} >> "$GITHUB_STEP_SUMMARY"
fi
- name: Collect container logs
if: failure()
run: |
cd e2e/environment
docker compose logs management --tail=500 --no-color > management.log 2>&1 || true
docker compose logs reverse-proxy --tail=500 --no-color > reverse-proxy.log 2>&1 || true
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: playwright-report
path: e2e/playwright-report/
- uses: actions/upload-artifact@v7
if: ${{ failure() }}
with:
name: playwright-traces
path: e2e/test-results/
- uses: actions/upload-artifact@v7
if: ${{ failure() }}
with:
name: management-logs
path: e2e/environment/management.log
- uses: actions/upload-artifact@v7
if: ${{ failure() }}
with:
name: reverse-proxy-logs
path: e2e/environment/reverse-proxy.log
-42
View File
@@ -1,42 +0,0 @@
name: run e2e tests
on:
push:
branches:
- main
pull_request:
jobs:
e2e_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: setup-node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: install playwright
run: npx playwright install
- name: install playwright deps
run: npx playwright install-deps
- name: create test environment
run: bash ./e2e-tests/create-test-env.sh
- name: run e2e tests
run: npx playwright test --workers 2
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: |
playwright-report/
test-results/
retention-days: 3
@@ -0,0 +1,34 @@
name: Sync deploy templates to S3
on:
push:
branches: [main]
paths:
- "templates/reverse-proxy/**"
permissions:
id-token: write
contents: read
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.DEPLOY_TEMPLATES_ROLE_ARN }}
aws-region: eu-central-1
- name: Validate CloudFormation template
run: |
aws cloudformation validate-template \
--template-body file://templates/reverse-proxy/netbird-proxy-cfn.yaml
- name: Upload CloudFormation template
run: |
aws s3 cp templates/reverse-proxy/netbird-proxy-cfn.yaml \
s3://netbird-deploy-templates/templates/netbird-proxy-cfn.yaml
+78
View File
@@ -0,0 +1,78 @@
name: Test cloud deploy config
on:
pull_request:
paths:
- "docker/init_react_envs.sh"
- "templates/reverse-proxy/**"
- "src/modules/reverse-proxy/clusters/ClusterCloudDeploy.tsx"
- ".github/workflows/test-cloud-deploy.yml"
permissions:
contents: read
jobs:
csp-connect-src:
name: CSP allows cloud provider APIs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install nginx
run: |
sudo apt-get update
sudo apt-get install -y nginx gettext-base
- name: Prepare nginx layout expected by the init script
run: |
sudo mkdir -p /etc/nginx/http.d /usr/share/nginx/html
echo 'add_header Content-Security-Policy "placeholder" always;' | sudo tee /etc/nginx/http.d/default.conf
sudo touch /usr/share/nginx/html/OidcTrustedDomains.js.tmpl
sudo systemctl start nginx
- name: Run init script with production-like env
env:
AUTH_AUTHORITY: https://auth.example.com
AUTH_CLIENT_ID: test-client
AUTH_AUDIENCE: test-audience
AUTH_SUPPORTED_SCOPES: openid profile email
USE_AUTH0: "false"
NETBIRD_MGMT_API_ENDPOINT: https://api.example.com
run: sudo -E bash docker/init_react_envs.sh
- name: Assert provider APIs are in connect-src
run: |
CSP=$(grep -o 'Content-Security-Policy "[^"]*"' /etc/nginx/http.d/default.conf)
echo "Generated CSP: $CSP"
CONNECT_SRC=$(echo "$CSP" | sed 's/.*connect-src \([^;]*\);.*/\1/')
echo "connect-src: $CONNECT_SRC"
status=0
for origin in https://api.hetzner.cloud https://api.digitalocean.com; do
if echo "$CONNECT_SRC" | grep -qF "$origin"; then
echo "OK: $origin"
else
echo "MISSING in connect-src: $origin"
status=1
fi
done
exit $status
cfn-template-url:
name: CloudFormation template URL is live
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract template URL from component and check it
run: |
URL=$(grep -o 'https://[^"]*netbird-proxy-cfn\.yaml' src/modules/reverse-proxy/clusters/ClusterCloudDeploy.tsx)
echo "CFN_TEMPLATE_URL: $URL"
test -n "$URL"
# Non-blocking: the template is published by sync-deploy-templates on
# the default branch, so it may not exist yet for a PR that only edits
# the URL. Warn instead of failing the check.
if curl -sfI "$URL" >/dev/null; then
echo "OK: template URL is live"
else
echo "::warning::CloudFormation template URL is not reachable yet: $URL"
fi
+32
View File
@@ -0,0 +1,32 @@
name: Unit Tests
on:
push:
branches:
- main
pull_request:
# Cancel superseded runs on the same ref so a new push doesn't queue behind an
# outdated one (mirrors build_and_push.yml).
concurrency:
group: unit-tests-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
vitest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: setup-node
uses: actions/setup-node@v5
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
+39 -21
View File
@@ -2,42 +2,60 @@
# dependencies
/node_modules
/node_modules.bkp
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
/out
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
src/auth_config.json
.idea
.eslintcache
src/.local-config*.json
/public/OidcServiceWorker.js
/public/OidcTrustedDomains.js
/e2e-tests/node_modules/
/e2e-tests/playwright-report/
/e2e-tests/test-results/
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# config
.local*config*.json
.test-config.json
e2e/playwright.env.json
e2e/fixtures/auth/*.json
e2e/test-results/
e2e/playwright-report/
/test-results/
/playwright-report/
.env
Caddyfile
docker-compose.yml
machinekey/
management.json
turnserver.conf
zitadel.env
playwright-report*.zip
.configs/.local-config.zitadel.json
.configs/.staging-config.json
.configs/.temp-config.json
.configs
/public/ironrdp-pkg/
/public/netbird.wasm*
.idea
src/.local-config*
.deepsec/
# Local SDK tarballs from the agent repo (packed by scripts, not committed)
vendor/
+1
View File
@@ -1,2 +1,3 @@
Mikhail Bragin (https://github.com/braginini)
Maycon Santos (https://github.com/mlsmaycon)
NetBird GmbH
+64
View File
@@ -0,0 +1,64 @@
## Contributor License Agreement
This Contributor License Agreement (referred to as the "Agreement") is entered into by the individual
submitting this Agreement and NetBird GmbH, Brunnenstraße 196, 10119 Berlin, Germany,
referred to as "NetBird" (collectively, the "Parties"). The Agreement outlines the terms and conditions
under which NetBird may utilize software contributions provided by the Contributor for inclusion in
its software development projects. By submitting this Agreement, the Contributor confirms their acceptance
of the terms and conditions outlined below. The Contributor further represents that they are authorized to
complete this process as described herein.
## 1 Preamble
In order to clarify the IP Rights situation with regard to Contributions from any person or entity, NetBird
must have a contributor license agreement on file to be signed by each Contributor, containing the license
terms below. This license serves as protection for both the Contributor as well as NetBird and its software users;
it does not change Contributor’s rights to use his/her own Contributions for any other purpose.
## 2 Definitions
2.1 “IP Rights” shall mean all industrial and intellectual property rights, whether registered or not registered, whether created by Contributor or acquired by Contributor from third parties, and similar rights, including (but not limited to) semiconductor property rights, design rights, copyrights (including in the form of database rights and rights to software), all neighbouring rights (Leistungsschutzrechte), trademarks, service marks, titles, internet domain names, trade names and other labelling rights, rights deriving from corresponding applications and registrations of such rights as well as any licenses (Nutzungsrechte) under and entitlements to any such intellectual and industrial property rights.
2.2 "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is or previously has been intentionally Submitted by Contributor to NetBird for inclusion in, or documentation of any Work.
2.3 "Contributor" shall mean the copyright owner or legal entity authorized by the copyright owner that is concluding this Agreement with NetBird. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
2.4 "Submitted" shall mean any form of electronic, verbal, or written communication sent to NetBird or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, NetBird for the purpose of discussing and improving the Work, but excluding communication that is marked or otherwise designated in writing by Contributor as "Not a Contribution".
2.5 "Work" means any of the products owned or managed by NetBird, in particular, but not exclusively, software.
## 3 Licenses
3.1 Subject to the terms and conditions of this agreement, Contributor hereby grants to NetBird and to recipients of software distributed by NetBird a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to reproduce by any means and in any form, in whole or in part, permanently or temporarily, the Contributions (including loading, displaying, executing, transmitting or storing works for the purpose of executing and processing data or transferring them to video, audio and other data carriers), including the right to distribute, display and present such Contributions and make them available to the public (e.g. via the internet) and to transmit and display such Contributions by any means. The license also includes the right to modify, translate, adapt, edit and otherwise alter the Contributions and to use these results in the same manner as the original Contributions and derivative works. Except for licenses in patents acc. to Sec. 3, such license refers to any IP Rights in the Contributions and derivative works. The Contributor acknowledges that NetBird is not required to credit them by name for their Contribution and agrees to waive any moral rights associated with their Contribution in relation to NetBird or its sublicensees.
3.2 Subject to the terms and conditions of this agreement, Contributor hereby grants to NetBird and to recipients of software distributed by NetBird a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license in the Contributions to make, have made, use, sell, offer to sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by the Contributor which are necessarily infringed by Contributor‘s Contribution(s) alone or by combination of Contributor’s Contribution(s) with the Work to which such Contribution(s) was Submitted.
3.3 NetBird hereby accepts such licenses.
## 4 Contributor’s Representations
4.1 Contributor represents that Contributor is legally entitled to grant the above license. If Contributor’s employer has IP Rights to Contributor’s Contributions, Contributor represent that he/she has received permission to make Contributions on behalf of such employer, that such employer has waived such IP Rights to the Contributions of Contributor to NetBird, or that such employer has executed a separate contributor license agreement with NetBird.
4.2 Contributor represents that any Contribution is his/her original creation.
4.3 Contributor represents to his/her best knowledge that any Contribution does not violate any third party IP Rights.
4.4 Contributor represents that any Contribution submission includes complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which Contributor is personally aware and which are associated with any part of the Contribution.
4.5 The Contributor represents that their Contribution does not include any work distributed under a copyleft license.
## 5 Information obligation
Contributor agrees to notify NetBird of any facts or circumstances of which Contributor become aware that would make these representations inaccurate in any respect.
## 6 Submission of Third-Party works
Should Contributor wish to submit work that is not Contributor’s original creation, Contributor may submit it to NetBird separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which Contributor are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
## 7 No Consideration
Unless compensation is mandatory under statutory law, no compensation for any license under this agreement shall be payable.
## 8 Final Provisions
8.1 Laws. This Agreement is governed by the laws of the Federal Republic of Germany.
8.2 Venue. Place of jurisdiction shall, to the extent legally permissible, be Berlin, Germany.
8.3 Severability. If any provision in this agreement is unlawful, invalid or ineffective, it shall not affect the enforceability or effectiveness of the remainder of this agreement. The parties agree to replace any unlawful, invalid or ineffective provision with a provision that comes as close as possible to the commercial intent and purpose of the original provision. This section also applies accordingly to any gaps in the contract.
8.4 Variations. Any variations, amendments or supplements to this Agreement must be in writing. This also applies to any variation of this Section 8.4.
+655 -7
View File
File diff suppressed because it is too large. Load diff
+38 -19
View File
@@ -1,4 +1,4 @@
# NetBird dashboard
# NetBird Dashboard
This project is the UI for NetBird's Management service.
@@ -10,6 +10,7 @@ See [NetBird repo](https://github.com/netbirdio/netbird)
The purpose of this project is simple - make it easy to manage VPN built with [NetBird](https://github.com/netbirdio/netbird).
The dashboard makes it possible to:
- track the status of your peers
- remove peers
- manage Setup Keys (to authenticate new peers)
@@ -17,49 +18,52 @@ The dashboard makes it possible to:
- define access controls
## Some Screenshots
<img src="./media/auth.png" alt="auth"/>
<img src="./media/peers.png" alt="peers"/>
<img src="./media/add-peer.png" alt="add-peer"/>
<img src="./src/assets/screenshots/peers.png" alt="peers"/>
<img src="./src/assets/screenshots/add-peer.png" alt="add-peer"/>
## Technologies Used
- NextJS
- ReactJS
- AntD UI framework
- Tailwind CSS
- [React Flow](https://reactflow.dev/) for the Control Center
- Auth0
- Nginx
- Docker
- Let's Encrypt
## How to run
Disclaimer. We believe that proper user management system is not a trivial task and requires quite some effort to make it right. Therefore we decided to
use Auth0 service that covers all our needs (user management, social login, JTW for the management API).
use Auth0 service that covers all our needs (user management, social login, JWT for the management API).
Auth0 so far is the only 3rd party dependency that can't be really self-hosted.
1. Install [Docker](https://docs.docker.com/get-docker/)
2. Register [Auth0](https://auth0.com/) account
3. Running Wiretrustee UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
3. Running NetBird UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
`AUTH0_DOMAIN` `AUTH0_CLIENT_ID` `AUTH0_AUDIENCE`
To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Configure Allowed Web Origins"
To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react) up until "Configure Allowed Web Origins"
4. Wiretrustee UI Dashboard uses Wiretrustee Management Service HTTP API, so setting `NETBIRD_MGMT_API_ENDPOINT` is required. Most likely it will be `http://localhost:33071` if you are hosting Management API on the same server.
4. NetBird UI Dashboard uses NetBird's Management Service HTTP API, so setting `NETBIRD_MGMT_API_ENDPOINT` is required. Most likely it will be `http://localhost:33071` if you are hosting Management API on the same server.
5. Run docker container without SSL (Let's Encrypt):
```shell
docker run -d --name wiretrustee-dashboard \
docker run -d --name netbird-dashboard \
--rm -p 80:80 -p 443:443 \
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
-e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> \
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
wiretrustee/dashboard:main
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMENT API URL> \
netbirdio/dashboard:main
```
6. Run docker container with SSL (Let's Encrypt):
```shell
docker run -d --name wiretrustee-dashboard \
docker run -d --name netbird-dashboard \
--rm -p 80:80 -p 443:443 \
-e NGINX_SSL_PORT=443 \
-e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> \
@@ -67,12 +71,27 @@ Auth0 so far is the only 3rd party dependency that can't be really self-hosted.
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
-e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> \
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
wiretrustee/dashboard:main
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMENT API URL> \
netbirdio/dashboard:main
```
## How to run local development
1. Install node 16
2. create and update the `src/.local-config.json` file. This file should contain values to be replaced from `src/config.json`
3. run `npm install`
4. run `npm run start dev`
1. Install [Node](https://nodejs.org/)
2. Create and update the `.local-config.json` file. This file should contain values to be replaced from `config.json`
3. Run `npm install` to install dependencies
4. Run `npm run dev` to start the development server
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing by modifying the code inside `src/..`
The page auto-updates as you edit the file.
## How to migrate from old dashboard (v1)
The new dashboard comes with a new docker image `netbirdio/dashboard:main`.
To migrate from the old dashboard (v1) `wiretrustee/dashboard:main` to the new one, please follow the steps below.
1. Stop the dashboard container `docker compose down dashboard`
2. Replace the docker image name in your `docker-compose.yml` with `netbirdio/dashboard:main`
3. Recreate the dashboard container `docker compose up -d --force-recreate dashboard`
+12
View File
@@ -0,0 +1,12 @@
[
{
"tag": "New",
"text": "The new NetBird desktop app is here - now available in the v0.75 release.",
"link": "https://netbird.io/knowledge-hub/netbird-v0-75-new-desktop-app",
"linkText": "Learn more",
"variant": "important",
"isExternal": true,
"closeable": true,
"isCloudOnly": false
}
]
Executable
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
#
# Build the netbird-dashboard Docker image for linux/amd64 and export it as a
# portable tarball that can be loaded and run on a remote server.
#
# The dashboard image is nginx serving a Next.js *static export*: the Dockerfile
# only COPYs the pre-built out/ directory, so we build out/ on the host first
# (the static files are arch-independent) and then assemble the amd64 image.
#
# Usage:
# ./build.sh # build + export netbird-dashboard.tar.gz
# IMAGE_TAG=v1 ./build.sh # override the image tag
# SKIP_BUILD=1 ./build.sh # reuse an existing out/ (skip npm build)
# ROOTLESS=1 ./build.sh # build the amd64-rootless image variant
#
set -euo pipefail
# Always run relative to this script's directory (the project root).
cd "$(dirname "$0")"
IMAGE_NAME="${IMAGE_NAME:-netbird-dashboard}"
IMAGE_TAG="${IMAGE_TAG:-amd64}"
DOCKERFILE="docker/Dockerfile"
CONTAINER_PORT=80
OUTPUT_SUFFIX=""
case "${ROOTLESS:-0}" in
0) ;;
1)
DOCKERFILE="docker/Dockerfile.rootless"
CONTAINER_PORT=8080
OUTPUT_SUFFIX="-rootless"
[[ "${IMAGE_TAG}" == *-rootless ]] || IMAGE_TAG="${IMAGE_TAG}-rootless"
;;
*)
echo "ERROR: ROOTLESS must be 0 or 1." >&2
exit 1
;;
esac
IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
OUTPUT="${OUTPUT:-${IMAGE_NAME}${OUTPUT_SUFFIX}.tar.gz}"
# Build the static export (out/) unless told to reuse an existing one. Next 16
# no longer runs ESLint during `next build`; types are checked by the build via
# tsc (verify separately with `npx tsc --noEmit`).
if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
if [[ ! -d node_modules ]]; then
echo "==> Installing dependencies (npm ci)"
npm ci
fi
echo "==> Building static export (next build) into out/"
npx next build
fi
if [[ ! -d out ]]; then
echo "ERROR: out/ not found — the static export did not build." >&2
exit 1
fi
echo "==> Building ${IMAGE} for linux/amd64"
docker build \
--platform linux/amd64 \
-f "${DOCKERFILE}" \
-t "${IMAGE}" \
.
echo "==> Exporting ${IMAGE} to ${OUTPUT}"
docker save "${IMAGE}" | gzip > "${OUTPUT}"
echo "==> Done: ${OUTPUT} ($(du -h "${OUTPUT}" | cut -f1))"
echo
echo "Copy and run on the server:"
echo " scp ${OUTPUT} user@your-server:/tmp/"
echo " docker load < /tmp/${OUTPUT##*/}"
echo
echo "Run (nginx serves on container port ${CONTAINER_PORT}). Set the runtime env vars the"
echo "dashboard templates at startup — for the Agent Network onboarding test,"
echo "flip NETBIRD_AGENT_NETWORK_ONLY=true:"
echo " docker run -d --name ${IMAGE_NAME} --restart unless-stopped \\"
echo " -p 8080:${CONTAINER_PORT} \\"
echo " -e NETBIRD_MGMT_API_ENDPOINT=https://your-mgmt:443 \\"
echo " -e AUTH_AUTHORITY=https://your-idp/realms/netbird \\"
echo " -e AUTH_CLIENT_ID=netbird-dashboard \\"
echo " -e AUTH_AUDIENCE=netbird-dashboard \\"
echo " -e AUTH_SUPPORTED_SCOPES='openid profile email' \\"
echo " -e USE_AUTH0=false \\"
echo " -e NETBIRD_AGENT_NETWORK_ONLY=true \\"
echo " ${IMAGE}"
Loaded 20 of 2060 files, more files were not shown because too many files have changed in this diff. Show more