listing_ebay/CLAUDE.md

9.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Repo layout

This is a monorepo with two independent Node packages:

  • BACKEND/ — AdonisJS 6 (TypeScript, ESM) HTTP API + Socket.IO server. Talks to MySQL via Lucid ORM and uses Redis for shared/persisted state. Connects to physical lab equipment (Cisco-style consoles, APC PDUs, network switches) over raw TCP/Telnet.
  • FRONTEND/ — Vite + React 19 + Mantine 8 SPA. Authenticates locally and connects to the backend's REST API and Socket.IO server.

There is no workspace tool — each side has its own package.json, node_modules, lockfile, and ESLint/TS config. Run npm commands from inside the relevant directory.

Common commands

Backend (cd BACKEND)

  • npm run dev — starts AdonisJS via node ace serve --hmr (HTTP + Socket.IO providers boot together).
  • npm run buildnode ace build.
  • npm startnode bin/server.js (production entry; the Socket.IO provider's ready() only boots the WS server when the entry is server.js).
  • npm testnode ace test (Japa). Suites are configured in adonisrc.ts: unit (tests/unit/**/*.spec.ts) and functional (tests/functional/**/*.spec.ts).
  • npm run lint / npm run format / npm run typecheck.
  • node ace migration:run — apply MySQL migrations from database/migrations/.
  • node ace db:seed — run seeders in database/seeders/ (e.g. prompt_ai_seeder.ts).
  • Run a single test: node ace test --files "tests/unit/foo.spec.ts" (Japa filter; --tags and --match also work).

Frontend (cd FRONTEND)

  • npm run dev — Vite dev server (default port 5173).
  • npm run buildtsc -b && vite build.
  • npm run lint — flat-config ESLint.
  • npm run preview — preview the production build.

Setup / environment

  • Both sides use .env (copy from .env.example). Backend reads SOCKET_PORT (default 8989), FRONTEND_URL (CORS origin), MySQL/Redis creds, Zulip stream/topic, LINK_WIKI, TIME_ZONE. Frontend reads VITE_BACKEND_URL and VITE_SOCKET_SERVER.
  • A local Redis server is required (the README documents apt install redis-server for Linux hosts).

Architecture — the parts that span files

Backend: HTTP API is thin; the real engine is the Socket.IO provider

BACKEND/providers/socket_io_provider.ts is registered in adonisrc.ts and only boots its WebSocket server when the process entry is server.js (so ace commands like migrations don't open the socket port). It exposes a static SocketIoProvider.io for the rest of the app.

Inside WebSocketIo.boot() it creates a separate http.createServer() listening on SOCKET_PORT (independent of the AdonisJS HTTP port) and registers a single big connection handler with ~30 socket events (connect_lines, write_command_line_from_web, run_scenario, open_cli/close_cli, control_apc, control_switch, run_all_dpelp, load_ios_router, etc.). When extending behavior, the pattern is: add a new socket.on(...) block and route it through handleLineOperation / handleStationOperation, which auto-reconnect the underlying TCP session if it has dropped.

The provider keeps in-memory maps that ARE the live system state:

  • lineMap: Map<lineId, LineConnection>
  • stationMap: Map<stationId, StationConnection>
  • apcsControl: Map<apcIp, APCController>
  • switchControl: Map<switchIp, SwitchController>
  • userConnecting: Map<userId, ...>

Every 10s saveState() serializes a slimmed lineMap (status forced to disconnected, output truncated to last 5000 chars, CLI ownership cleared) into Redis under key socket_state. On boot, restoreState() rehydrates it. Per-line history is stored in Redis sorted sets keyed station:{stationId}:line:{lineId}:history.

setTimeoutConnect(lineId, conn, 8h) arms an idle-disconnect interval per connection; keepConnectAPC / keepConnectStation arm the inverse — periodic \r\n / ENTER keep-alives. Keep these paired when adding new connection types or sessions silently die / outlive their usefulness.

Backend: app/services/ is a hierarchy of TCP session classes

Each class wraps a net.Socket to a piece of lab hardware and exposes a config object that the Socket.IO layer mutates and broadcasts:

  • station_connection.ts — top-level Cisco station console (used to issue clear line N, show line, etc.).
  • line_connection.ts — individual serial line on a station; the heart of test orchestration. Owns scenario execution (runScript), DPELP flow, IOS/license loading (loadIosRouter, loadIosSwitch, loadLicenseRouter, loadLicenseSwitch), physical port testing (PhysicalPortTest from physical_test_service.ts), and AI log classification (uses PromptAi model + axios calls).
  • apc_connection.ts — APC PDU outlet control (on/off/restart/reconnect, navigateToOutlets).
  • switch_connection.ts — managed switch port + PoE control.

When a line emits data, line_connection.ts parses it through TextFSM-style templates (app/ultils/templates/) and helper.ts parsers, and emits Socket.IO events (line_output, data_textfsm, running_scenario, feature_tested, summary_tested, test_port_physical, etc.) that the frontend subscribes to in App.tsx's big useEffect.

Backend: routes and AdonisJS subpath imports

start/routes.ts defines REST endpoints grouped by resource (api/stations, api/lines, api/logs, api/users, api/models, api/scenarios, api/auth, api/ticket, api/brands, api/categories, api/config-ram, api/keywords, api/prompt-ai, atc/health-check, api/ios, api/license). Controllers live under app/controllers/, models under app/models/.

The project relies heavily on AdonisJS subpath imports declared in package.json#imports — always use these, not relative paths:

  • #controllers/*, #models/*, #services/*, #middleware/*, #start/*, #config/*, #exceptions/*, #validators/*, #providers/*, #policies/*, #abilities/*, #database/*, #tests/*, #mails/*, #listeners/*, #events/*.

Note: socket_io_provider.ts itself uses relative paths to reach into app/ (e.g. '../app/services/line_connection.js') because it lives outside app/. Match the convention of the file you're editing.

Frontend: single-page app driven by Socket.IO events

  • src/main.tsxsrc/App.tsx is the entire mounted tree. Main (default export) wraps App in MantineProvider + SocketProvider and gates on localStorage.user; if absent, it renders PageLogin instead.
  • src/context/SocketContext.tsx creates a single socket.io-client instance authed with { userId, userName } and provides it via useSocket(). URL comes from VITE_SOCKET_SERVER (default http://localhost:8989/).
  • App.tsx keeps the canonical stations: TStation[] state. Most socket events arrive as line-level deltas and are funneled through updateValueLineStation(lineId, updates, stationId), which fans out to selectedLine / selectedLines in lockstep. line_output is special: its text is buffered in lineBuffersRef (a Map<lineId, string>) and flushed every ~50ms via flushBuffers to keep terminal updates from causing per-character React renders.
  • Large log payloads arrive as response_content_log chunks ({ fileId, chunkIndex, totalChunks, chunk }) and are reassembled into a single string before being shown.

UI is built with Mantine 8 (@mantine/core, @mantine/dates, @mantine/form, @mantine/hooks, @mantine/notifications), @dnd-kit/* for the draggable station tabs (DragTabs.tsx), @tabler/icons-react, and xterm (+ @xterm/addon-fit) for the terminal modal.

Conventions worth knowing before editing

  • Folder name typos are intentional (or at least entrenched): backend uses app/ultils/ (helpers, types, templates) and frontend uses src/untils/. Imports across the codebase reference these names; do not rename them as a "cleanup".
  • Two TCP-session lifecycles to remember: idle-timeout (setTimeoutConnect, default 8h) and keep-alive (keepConnectAPC 40s, keepConnectStation 120s). When you add a new long-lived connection, wire both — otherwise it'll either disconnect silently or hold a slot forever.
  • State that survives restarts lives in Redis, not memory: socket_state (full lineMap snapshot) and station:{id}:line:{id}:history sorted sets. If you add per-line state that should persist across restarts, extend saveState / restoreState.
  • Hot-reload boundaries (hotHook.boundaries in BACKEND/package.json) are limited to controllers and middleware. Editing a service or provider file may require a full restart for changes to apply.
  • Comments and string literals frequently mix Vietnamese and English — preserve existing language when modifying nearby code.
  • Auth is custom + minimal: routes under api/auth/login and api/auth/register are public; start/kernel.ts registers an auth named middleware via @adonisjs/auth, but most routes in start/routes.ts are not currently behind it. The frontend gates on localStorage.user only.