1
Web Dashboard
anti edited this page 2026-04-18 06:05:22 -04:00

Web Dashboard

The DECNET Web Dashboard is a React + TypeScript + Vite single-page app that talks to the FastAPI backend exposed by the DECNET API server. It is the operator-facing control surface for inspecting deckies, attackers, live logs, bounties, and runtime configuration.

Related: REST API - Environment variables - Security & stealth.

Running

Two processes are involved: the API server (FastAPI) and the web front-end (static SPA with a thin /api/* reverse proxy).

# Start the FastAPI backend (serves the REST API on DECNET_API_PORT).
decnet serve-api

# Serve the Web Dashboard (static SPA).
decnet web \
    --host 0.0.0.0 \
    --web-port 8080 \
    --api-port 8000

decnet web loads the pre-built bundle from decnet_web/dist/ and refuses to start if that directory is missing. The handler proxies any request whose path starts with /api/ to the backend on --api-port, so the SPA can use relative URLs and no CORS round-trip is needed in the default topology. Add --daemon to detach into the background.

Environment variables

Variable Default Purpose
DECNET_WEB_HOST 127.0.0.1 Bind address for the SPA server.
DECNET_WEB_PORT 8080 Port for the SPA server.
DECNET_ADMIN_USER admin Admin username seeded on first boot.
DECNET_ADMIN_PASSWORD admin Admin password seeded on first boot.
DECNET_JWT_SECRET required HS256 signing secret, must be at least 32 characters.
DECNET_CORS_ORIGINS http://<web_host>:<web_port> Comma-separated allowlist for backend CORS.

The CORS default resolves wildcard bind addresses (0.0.0.0, ::) to localhost when building the default origin. Override with something like:

DECNET_CORS_ORIGINS=http://192.168.1.50:9090,https://dashboard.example.com

Admin credentials

The literal pair admin / admin is a known-bad default. The web layer refuses to start with those values outside pytest: set both DECNET_ADMIN_USER and DECNET_ADMIN_PASSWORD to non-default values before running decnet web or decnet serve-api in production. See the admin credentials section in Environment variables for the exact enforcement rules.

On first boot the admin record is seeded with must_change_password=True, and the frontend forces a password change on the next login.

Pages

The routes are defined in decnet_web/src/App.tsx. All pages share the Layout chrome (top bar with search + logout, left nav).

  • Dashboard (/, Dashboard.tsx) - Landing view. Summary tiles for decky count, attacker count, recent log volume, and top-level activity charts. The top-bar search box is threaded in as searchQuery so the dashboard can filter its recent-events pane inline.
  • DeckyFleet (/fleet, DeckyFleet.tsx) - Fleet control panel. Lists every decky with status, IP, services, and mutation interval. Admin-only controls let you redeploy from an INI payload, mutate or kill individual deckies, and retune mutation intervals. Backed by the fleet/ router group.
  • LiveLogs (/live-logs, LiveLogs.tsx) - Streaming log tail across the whole fleet. Events are parsed with utils/parseEventBody.ts to pull out attacker IP, decky, service, and RFC 5424 structured data fields. An artifact drawer (ArtifactDrawer.tsx) pops open when a log line references an uploaded payload.
  • Attackers (/attackers, Attackers.tsx) - Grouped view of observed source IPs with hit counts, first/last seen, and targeted services. Click through to the detail page.
  • AttackerDetail (/attackers/:id, AttackerDetail.tsx) - Full per-attacker timeline: every log line tied to that IP, captured credentials and commands, any uploaded artifacts, and bounty status if applicable.
  • Config (/config, Config.tsx) - Runtime configuration and user management. Admins can view and edit the running INI config, reinitialize the fleet, and create, update, or delete dashboard users. Non-admins cannot reach this page; see "Server-side UI gating" below.
  • Bounty (/bounty, Bounty.tsx) - Displays bounty offers and redemption state attached to particular attacker behaviors. Read-only view over the bounty/ router group.

Authentication

The backend exposes POST /auth/login (see decnet/web/router/auth/api_login.py). The handler loads the user via a cached lookup, verifies the password with bcrypt off the event loop, and returns a Token payload:

{
  "access_token": "<JWT>",
  "token_type": "bearer",
  "must_change_password": false
}

JWT details (decnet/web/auth.py):

  • Algorithm: HS256.
  • Secret: DECNET_JWT_SECRET (must be at least 32 characters; short or known-bad values abort startup).
  • Lifetime: 1440 minutes (24 hours).
  • Claims: uuid (user UUID), iat, exp.

The SPA stores the token in localStorage and decodes the exp claim on boot to decide whether to show the login screen (App.tsx).

must_change_password flow

When the login response carries must_change_password: true the SPA forces a password-change dialog before any other page loads. Internally, the FastAPI dependency get_current_active_user returns HTTP 403 for any protected route while the flag is set, so the rest of the API is effectively gated until the user calls POST /auth/change-password, which clears the flag via repo.update_user_password(..., must_change_password=False).

Server-side UI gating

Admin-only UI must be gated by the backend, not by client-side role checks. Every mutating or privileged endpoint under the fleet/, config/, and artifacts/ router groups is protected by the require_admin FastAPI dependency (decnet/web/dependencies.py). The SPA may hide or disable admin controls for UX, but that is a presentation convenience only - a non-admin user who hand-crafts requests still gets HTTP 403 from the server. Do not introduce features whose only access check is if user.role === 'admin' in React; that check must exist on the server side as well.

Client build

The front-end lives in decnet_web/ and is a standard Vite project.

cd decnet_web
npm install
npm run build        # production bundle -> decnet_web/dist/
npm run dev          # hot-reload dev server (proxies /api/* to the backend)

decnet web expects the production bundle at decnet_web/dist/ relative to the installed package (Path(__file__).parent.parent / "decnet_web" / "dist" in decnet/cli.py). If you are working on the SPA, rebuild before running decnet web, or use npm run dev and point your browser at the Vite dev server during development.