Skip to content

UI

Four files:

File Content
socrates.html HTML shell (repo root) - no inline CSS or JS
static/theme-boot.js Restores the saved theme before first paint
static/socrates.css All styles
static/socrates.js All JavaScript

socrates.html loads them via <script src> and <link> tags - theme-boot.js parser-blocking in <head>, so the theme applies before anything draws. D3 and d3-sankey are vendored in static/ for offline use; CyberChef is baked into the container image and served at /cyberchef/ (see Architecture).

UI States

Main Screen (no analysis loaded)
  ├── Sample PCAP/log/binary/email cards (built in - load-builtin-sample → POST /api/load-sample)
  ├── URL input + file upload
  └── Previous analyses list

Analysis View (analysis loaded)
  ├── App header (SO-CRATES logo/home link, file name, MD5, date range, notes/reanalyze/delete icons, gear menu)
  ├── tshark line (PCAP analyses: what tshark is adding, and the key to the cards' tshark dot)
  ├── Search bar
  ├── File Info card (binary-file analyses only)
  ├── Filter Bar (active search and filters as removable chips)
  ├── Stats Grid (clickable event-type cards, shows the filtered count alone when a filter is active - see `buildStats()` in filtering.md)
  ├── Sankey Diagram (collapsible section - Source IP → Dest IP → Dest Port, reflects current filters)
  ├── Aggregation Tables (collapsible section - frequency counts per column)
  └── Data Table (the selected tab's rows; a row expands into its detail panel)

JavaScript Architecture

Global state:

let allEvents = [];          // Loaded for "All Events" tab
let eventTypes = [];         // available types for current analysis
var currentMd5 = '';         // current analysis MD5 (var, not let - see below)
var currentFileName = '';    // display name (var, not let - see below)
var currentNotes = '';       // per-analysis freeform notes (var, not let - see below)
var currentFilters = {};     // {columnName: value | {include, exclude}} — global, flat (var, not let - see below)
let currentSearch = [];      // server-side full-text search terms (array)
let baseEventStats = {};     // unfiltered per-type totals (baseline for the tab set)
var advancedMode = false;    // advanced toggle state (var, not let - see below)
let tabDataCache = {};       // cached event data per type

currentMd5/currentFileName/currentNotes/currentFilters/advancedMode (and a couple of others, e.g. truncatedTypes) are deliberately declared with var rather than let/const - the JSDOM test harness (tests/jsdom_helper.py) assigns/reads them via separate window.eval() invocations, and only var/function declarations attach to the actual global object persistently across those separate evaluations.

Key function groups:

Group Functions Purpose
Navigation showWelcome(), loadAnalysis(), showTab(), showWelcomeUI(), showAnalysisUI() Screen/tab switching
Keyboard Navigation navigateStatTabs(), activeColumnStatCards(), navigateSampleCards(), navigateVertical(), seedVerticalNavSelectionIfStale(), navigateFilterBarItems(), focusNewestFilterChip(), focusFilterBarOrFirstCard(), navigateThemeTiles(), activateKeyboardSelection(), isNavigableKeyContext() Arrow-key/Enter navigation (see Usage)
Command Palette AUTOCOMPLETE_COMMANDS, openAutocompleteModal(), filterAutocomplete(), autocompleteMatchesQuery(), autocompleteMatchScore(), activateAutocompleteSelection() Type-anywhere command list (see Usage)
Data Loading loadTabData(), loadFromUrl(), uploadPcap(), checkStatus() Fetch data from API
Rendering buildStats(), buildSections(), buildSection(), buildAllEvents(), buildRowForEvent(), updateSankeyDiagram() Build HTML
Aggregation buildAggregationTablesCore(), buildAggregationTables(), buildAggregationTablesAll(), buildAggregationsSection(), buildAggregationsSectionAll() Frequency grids
Search performSearch(), clearSearchTerm(), refreshAnalysisData() Full-text search via server
Filtering applyFilters(), clearFilter(), clearAllFilters(), getFilteredEvents(), getSankeyEvents(), refreshCurrentView() Column filter management
Streams downloadPcap(), loadAsciiTranscript(), loadHexdumpData(), switchStreamView(), togglePacket(), toggleRow() Stream analysis
Modals closeAllModals(), showNotesModal(), closeNotesModal(), saveAnalysisNotes(), openAnalysisNotesFromList(), showRulesModal(), closeRulesModal(), triggerRulesetUpdate(), isRulesetStale() Notes editing and Rules-modal management, shared modal cleanup
Utilities escapeHtml(), formatEvent(), extractValue(), extractAllValue(), getColumnsForType(), clearAnalysisContainers() Helpers

Column System

Each event type has its own column set. The "All Events" view uses a unified column set.

Shared columns (every network event type): Time, Protocol, Source IP, Source Port, Dest IP, Dest Port. Log events and Sigma alerts have their own, data-dependent columns instead, and an email analysis's tabs (Emails, Links, File Info, File Alerts) have networkless ones (EMAIL_MODE_COLUMNS) and no All Events view.

Per-type columns: e.g. Alert/Category/Severity (alerts), Query/Type (DNS), Method/Host/URL/Status (HTTP). Every event type on the Event Types page has its own set - 30+ types by now - defined in getColumnsForType() (static/socrates.js), which is the source of truth; this doc intentionally doesn't enumerate all of them; a full list here would just drift out of sync with every new protocol added (the same problem already found and fixed once in filtering.md's old "Column Overlap" table).

All-events columns: Type (event type), Detail (type-specific summary)

Filtering Design

Filters are global - currentFilters is one flat object keyed by column name, whose value is either an exact-match string or an {include, exclude} list pair. matchesCurrentFilters() applies every active filter to every tab: a filter on a column the current tab doesn't have compares against an empty value, so it excludes those rows rather than being skipped - a DNS Query filter matches no HTTP events, so the HTTP card drops out of the stats grid (buildStats() omits zero-count types).

See filtering.md for full details.