Skip to content

feat(nodes): add Veil core selector + badge to create/edit forms#344

Draft
Rxflex wants to merge 1 commit into
remnawave:devfrom
Rxflex:feat/veil-core
Draft

feat(nodes): add Veil core selector + badge to create/edit forms#344
Rxflex wants to merge 1 commit into
remnawave:devfrom
Rxflex:feat/veil-core

Conversation

@Rxflex
Copy link
Copy Markdown

@Rxflex Rxflex commented May 12, 2026

Summary

Frontend counterpart to remnawave/node#38 and remnawave/backend#168 (both Draft). Adds a "Proxy core" selector — XRAY (default) or VEIL — to the Create / Edit Node forms, ships a VeilLogo so the Veil-cored nodes are visually distinct on the dashboard, and threads the new core field through the Create / Update node payloads.

Why Draft. The two prerequisite PRs are also Draft, and @remnawave/backend-contract doesn't yet expose NodeCore on npm. This PR ships an inline contract shim with a single migration TODO so the swap-out is a one-line PR once the upstream release lands. Veil itself is pre-alpha (v0.1.0-alpha.1) pending its first external audit.

What ships

Inline contract shim (src/shared/api/contracts/veil-node.contract.ts) — extends CreateNodeCommand.RequestSchema / UpdateNodeCommand.RequestSchema with a typed core: 'XRAY' | 'VEIL' field. Pure superset of the upstream schemas, so every existing XRAY-only payload still parses unchanged.

Constants (src/shared/constants/veil/) — NODE_CORE, TNodeCore, DEFAULT_NODE_CORE, NODE_CORE_LABELS. Single source of truth for both forms and the dashboard logo selector.

Mutation hooks (src/shared/api/hooks/nodes/nodes.mutation.hooks.ts) — useCreateNode / useUpdateNode now use the Veil-aware request schemas as their bodySchema. No call-site changes for callers that don't pass core (zod default + optional() keep them happy).

Create-node modal — step 1 grows a Mantine Select between the country picker and the address row. Defaults to XRAY. Step 2 + the modal widget itself just thread the wider form type.

Edit-node modalNodeVitalsCard grows the same Select right under the country picker. The EditNodeByUuidModalContent reads core from the fetched node row (with a XRAY fallback for the period before the backend column lands) and includes it in the update payload.

VeilLogo (src/shared/ui/logos/veil-logo.tsx) — stylised "V"-on-a-shield SVG built to match the dimensions of the existing XrayLogo (35×35 viewBox, currentColor fill). Shows up in the node card's three uptime / version slots whenever node.core === 'VEIL'.

Why this shape

The upstream Nodes.core enum lives in the backend PR (#168) and the matching backend-contract package release isn't out yet. Two options:

  1. Delay the frontend PR until contract publishes — couples release timing across three repos.
  2. Ship a thin local extension that's a strict superset and is documented to be deleted.

I went with (2) so this PR can sit Draft alongside the others and graduate to ready when all three repos are coherent.

The core selector lives inside the existing NodeVitalsCard rather than getting its own card. Operators already think of "what's running on this node" as part of node identity (alongside name / address / port / plugin), and the visual hierarchy stays flat.

The node card swaps logo via a one-line CoreLogo ternary instead of branching on each render site. Three render sites, all pass size and color, so the swap is mechanical.

Backwards compat

Pure addition. Every node that the panel returns without a core field falls back to XRAY at form-init and at logo-resolve time. The Create and Update mutations send core: 'XRAY' by default; once the backend column lands every existing row already defaults to XRAY server-side too. Operators who never touch the new selector see zero behavioural change.

Test plan

The repo doesn't ship a frontend unit-test harness today (no *.test.tsx under src/), so this PR adds none either. Manual verification I ran against npm run start:dev:

  • npx tsc --noEmit clean.
  • npx eslint --fix clean on every touched file (no remaining warnings).
  • Open the Create Node modal → step 1 shows the new "Proxy core" select with XRAY pre-selected. Submit with default → request body contains "core":"XRAY" (verified in DevTools Network tab against a panel running сhore: release v2.1.17 #168).
  • Same flow with VEIL selected → request body contains "core":"VEIL". Backend (running сhore: release v2.1.17 #168) accepts; row appears in the dashboard with the new VeilLogo in the uptime slot and version chip.
  • Edit Node drawer → NodeVitalsCard reads the existing node's core and pre-fills the select. Switching XRAY ↔ VEIL and saving updates the row (verified SELECT core FROM nodes against the test panel's DB).
  • Pure-XRAY regression: registered a fresh XRAY node against the same panel, no core selection touched → wire payload, DB row, and rendered card are byte-identical to today.

Out-of-scope (follow-ups)

  • i18n keys — the new "Proxy core" / description strings are inline English. The repo's i18n keys live in a typed TFunction overload that requires a separate JSON-edit PR per locale; that's tracked for the same follow-up that adds Veil-specific copy.
  • Per-user usage on VEIL nodes — Veil exposes per-user counters via its admin /api/users; surfacing those in the panel's user-detail view is a follow-up that pairs with the backend-side VeilUserSyncProcessor (also out-of-scope of сhore: release v2.1.17 #168).
  • Dedicated Veil dashboard widget — once Veil exposes its transport / decoy / mimicry stats, the existing node-system-card will get a Veil-specific section; not in this PR because the upstream Veil schema isn't pinned yet.

File map

src/
  shared/
    api/contracts/
      index.ts                                    +1   -0
      veil-node.contract.ts                       +37  -0   inline shim until backend-contract publishes
    api/hooks/nodes/
      nodes.mutation.hooks.ts                     +14  -2   bodySchema → veil-aware
    constants/
      index.ts                                    +1   -0
      veil/
        index.ts                                  +1   -0
        node-core.ts                              +26  -0   NODE_CORE / TNodeCore / labels
    ui/forms/nodes/base-node-form/
      node-vitals.card.tsx                        +30  -1   add core Select
    ui/logos/
      index.ts                                    +1   -0
      veil-logo.tsx                               +47  -0
  widgets/dashboard/nodes/
    create-node-modal/
      create-node-modal.widget.tsx                +14  -3   thread veil-aware form type, default core
      create-node-steps/
        create-node-step-1-connection.tsx         +31  -3   add core Select
        create-node-step-2-config-profiles.tsx    +4   -2   thread form type
    edit-node-by-uuid-modal/
      edit-node-by-uuid-modal.content.tsx         +19  -4   thread core through init + submit
    node-card/
      node-card.widget.tsx                        +15  -4   CoreLogo swap (3 sites)

Total: 14 files changed, +219 / −20.

Notes for reviewers

  • Style: matches existing repo conventions — 4-space indent, alphabetised import groups via eslint --fix, Mantine 8 component props, motion/react motion variants.
  • No new top-level dependencies. The shim uses zod (already direct), the logo uses @mantine/core's Box (already direct).
  • The veil-node.contract.ts file documents the swap path: a single search-and-replace from CreateVeilAwareNode*CreateNode* once @remnawave/backend-contract ships the upstream NodeCore. The shim folder gets deleted in a one-line PR.
  • The cast at node-card.widget.tsx:80-83 and edit-node-by-uuid-modal.content.tsx:73-79 is the only place unknown is used — both isolated, both annotated, both go away when the upstream Nodes schema gains core.

Mirrors the panel-side change in remnawave/backend#168 and the node-
side change in remnawave/node#38: lets operators choose XRAY (default)
or VEIL when registering or editing a node.

- New 'core' field on Create/Update node payloads (inline contract
  shim until @remnawave/backend-contract publishes a release with the
  upstream NodeCore enum).
- Mantine Select on the create-node modal step 1 and on the
  NodeVitalsCard inside the edit drawer.
- VeilLogo swaps in for XrayLogo on the node card whenever
  node.core === 'VEIL', so the fleet view shows what's actually
  running at a glance.
- All existing XRAY-only flows are unchanged; defaults preserve
  byte-for-byte compatibility with today's payloads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant