Docs GitHub
-- --
Theme:
Language:
EN فا

UI#

bgscan’s terminal UI is built on BubbleTea (the Elvish v2 fork at charm.land/bubbletea/v2) and Lipgloss for styling. The architecture is a component tree with an overlay dialog system and a centralized layout manager.


Package map#

PackageResponsibility
ui/main/appRoot BubbleTea model. Manages layout, header/body/footer, and overlay dialog stack.
ui/main/headerTop bar — app title and branding.
ui/main/bodyCentral content area — holds a component stack (main menu → sub-screens).
ui/main/footerStatus bar — app version, current screen name, goroutine count, memory usage.
ui/components/basicReusable widgets: confirm, crud, input, inspector, logview, menu, notice, picker, progress, table, tabs.
ui/components/inspectorPer-protocol settings forms (DNS, general, HTTP, ICMP, TCP, Xray).
ui/components/menusScreen-level menus: entry (main), logs, outbound, scantype, settings, targetsource.
ui/components/tablesData tables: iplist, ipviewer, outbounds, resultlist.
ui/components/scannerLive scan view — tabbed progress bars + result tables per stage.
ui/sharedShared infrastructure: layout, dialog, env (keys/modes), component interface, validation.
ui/themeDark/light/auto color palettes + huh form theme adapter.

Component interface#

Every screen, widget, and dialog implements ui.shared.ui.Component:

type Component interface {
    ID() ComponentID
    Name() string
    Init() tea.Cmd
    Update(tea.Msg) (Component, tea.Cmd)
    View() string
    OnClose() tea.Cmd
    Mode() env.Mode
}
  • ID — UUID generated by NewComponentID(). Used to match open/close messages.
  • Name — human-readable label shown in the footer status bar.
  • Init — called once when mounted. Return a tea.Cmd for async setup.
  • Update — message router. Returns the updated component and any commands.
  • View — renders the component as a string.
  • OnClose — cleanup when removed from the stack.
  • Mode — determines which keys act as back/quit (see Input modes).

Root model (app)#

ui/main/app is the top-level BubbleTea model. It holds three base components and a dialog stack:

type model struct {
    layout           *layout.Layout
    dialog           []ui.Component
    dialogPlacements map[ui.ComponentID]*dialogPosition
    header           ui.Component
    body             ui.Component
    footer           ui.Component
}

Message routing (app/update.go)#

The root Update is the central router:

  1. tea.WindowSizeMsg — recalculates layout.Update(width, height).
  2. tea.KeyPressMsgctrl+c quits immediately; ctrl+t dumps goroutines.
  3. Overlay intercept — if dialogs exist, the top dialog consumes all key input. Back/quit keys close it.
  4. dialog.OpenDialogMsg — pushes a new overlay onto the stack with placement metadata.
  5. ui.CloseComponentMsg — removes the matching overlay by ID and runs its OnClose.
  6. Base dispatch — if no overlay blocks, the message fans out to header, body, and footer.

Rendering (app/view.go)#

The root View renders:

┌─────────────────────────────────┐
│           header                │
├─────────────────────────────────┤
│                                 │
│           body                  │  ← component stack (menu → screen)
│                                 │
├─────────────────────────────────┤
│           footer                │  ← version · status · goroutines · mem
└─────────────────────────────────┘
   + overlay dialogs (centered/positioned)

Overlays are rendered on top via bubbletea-overlay. Minimum terminal size is 75×35; below that a “terminal too small” message is shown.


Body — component stack#

ui/main/body holds a stack of components. The top of the stack is the active screen.

components[0] = mainMenu (entry)   ← always present
components[1] = scan source picker ← pushed on top
components[2] = scan type picker    ← pushed on top
components[3] = scanner view        ← pushed on top

Navigation messages:

MessageEffect
ui.OpenComponentMsgPush a new component onto the stack
ui.CloseComponentMsgPop a specific component by ID
ui.ResetComponentStacksMsgClear the stack back to the root menu
Back key (b / Esc)Pop the top component (if stack > 1)
Quit key (q)Show exit confirmation dialog

Only the top component receives Update. When it’s popped, the next one down becomes active. The footer status bar updates to show the new top component’s Name().


Overlay dialog system#

ui/shared/dialog provides popup dialogs rendered on top of the base layout.

type OpenDialogMsg struct {
    Component ui.Component
    XPos      DialogPosition   // Top, Right, Bottom, Left, Center
    YPos      DialogPosition
    XOffset   int
    YOffset   int
}

Dialogs use the rmhubbert/bubbletea-overlay library to position themselves. Most dialogs center on screen; file pickers and confirm dialogs may use different positions.

The dialog stack in app.model supports multiple nested overlays — the topmost consumes all input.


Input modes#

ui/shared/env/type.go defines three modes that control how keyboard input is interpreted:

ModeDescriptionBack keysQuit keys
NormalModeDefault navigationb, Backspace, Escq, ctrl+c
InputModeTyping in a text field/pickerEscctrl+c
ScanModeA scan is running(none — UI controls handle pause/stop)ctrl+c

IsBackKey(msg, mode) and IsQuitKey(msg, mode) in env/keys.go check the mode-specific key sets. This prevents quit/back from firing while the user is typing in an input field.


Layout#

ui/shared/layout/layout.go is a stateful geometry calculator. It precomputes the position and size of each region (header, body, footer) whenever the terminal is resized:

type Layout struct {
    Terminal TerminalSize   // raw terminal dimensions
    Content  ContentSize    // drawable area (after margins/padding)
    Header   ComponentSize
    Body     ComponentSize
    Footer   ComponentSize
}

Layout.Update(width, height) is called on every tea.WindowSizeMsg. All components receive a reference to the shared *Layout so they can query their region without recomputing.


Reusable widgets (basic)#

ui/components/basic/ contains the building-block widgets used across screens:

WidgetPurpose
confirmYes/no confirmation dialog (used for exit, delete)
crudGeneric CRUD table interface (IP lists, outbounds) with a Provider abstraction
inputInput dialog shell + typed input widgets (textinput, textarea, selectinput, multiselect, toggleinput)
inspectorGeneric key-value settings editor with field formatting and type adapters
logviewScrollable streaming log viewer (core/ui/debug)
menuVertical keyboard-navigable menu list
noticeTransient notification banner
pickerFile picker dialog
progressAnimated progress bar with ETA and rate display
tableSortable, scrollable data table
tabsTabbed container (used by scanner view for per-stage tabs)

Each widget follows the standard Component interface and is designed to be composed into larger screens.


Settings inspectors#

ui/components/inspector/ contains per-protocol settings forms:

FormConfig it edits
generalgeneral_settings.toml
icmpicmp_settings.toml
tcptcp_settings.toml
httphttp_settings.toml
dnsdns_settings.toml
xrayxray_settings.toml

Each inspector reads the current config via config.GetXxx(), renders editable fields, and on save calls config.SaveXxxConfig(). Fields can be dynamic — for example, TLS options only appear when HTTPS is selected, and DNSTT/SlipStream fields only show when those probes are enabled.

The generic basic/inspector widget handles the field rendering, formatting, and type adaptation; the per-protocol packages just define which fields to show and how to map them to the config struct.


MenuTriggered fromWhat it shows
entryApp startMain menu (Run Scan, IP Files, Result Files, Settings, Xray Outbounds, Logs)
targetsourceRun ScanSource picker (IP List vs Result List)
scantypeAfter source selectionScan type picker (ICMP, TCP, HTTP, DNS, Xray)
settingsSettings menu itemSettings category picker
outboundmenuXray Outbounds menu itemOutbound management
logsLogs menu itemLog category picker (core/ui/debug)

Tables (ui/components/tables)#

TableData sourceProvider
iplistips/ directoryiplist.Registry
resultlistresults/ directoryresult.Registry
outboundsXray outbound configsxray package
ipviewerIn-memory result setscanner component

Tables use the basic/table widget and a Provider interface that supplies rows and handles column sorting.


Scanner view#

ui/components/scanner is the live scan dashboard. It wires the Scanner to the UI:

  • One tab per scan stage (via basic/tabs).
  • Each tab shows a progress bar (basic/progress) and a live result table (tables/ipviewer).
  • Stage status is tracked per-stage: Waiting → PreProcess → Scanning → Ended / Error.
  • Progress updates flow via engine.ScanHooks.OnProgressProgress message → scanner component Update.
  • Successful results are buffered in per-stage batches and rendered in the ipviewer table.

Controls during a scan: pause/resume, stop, switch tabs to view different stages.


Theming#

ui/theme/theme.go provides a centralized color palette:

type Theme struct {
    Primary       color.Color
    Secondary     color.Color
    Border        color.Color
    BorderActive  color.Color
    Text          color.Color
    Muted         color.Color
    Info          color.Color
    Error         color.Color
    Success       color.Color
    Orange        color.Color
    Yellow        color.Color
    Purple        color.Color
    ProgressStart color.Color
    ProgressEnd   color.Color
}

Modes#

ModeBehavior
ModeAutoDetects terminal background via $COLORFGBG and picks dark/light
ModeDarkForces the Dark palette
ModeLightForces the Light palette

theme.Init() is called during startup. Components retrieve the active palette via theme.Current() — never hardcode colors.

Huh integration#

ui/theme/huh.go builds a huh.Theme from the centralized palette so that form fields (select, confirm, text input) rendered by the huh library match the rest of the UI instead of huh’s defaults. This is used by the input and inspector widgets.


Debugging#

Goroutine dump#

Pressing Ctrl+T at any time triggers dumpGoroutines() in app/update.go:

  • Logs the total goroutine count.
  • Writes the full runtime/pprof goroutine profile to the debug log.
  • Filters and logs lines containing [chan receive], [chan send], [select], [IO wait], [sleep] — useful for spotting stuck workers or probe leaks.

Runtime stats#

The footer component polls Go runtime metrics every second:

  • runtime.NumGoroutine() — goroutine count
  • runtime.ReadMemStats() — heap alloc and sys memory

These display in the status bar and are useful for monitoring scan resource usage in real time.


  • Architecture — high-level project layout
  • Core — scanner engine and probe interface