- Map init: requestAnimationFrame loop until container has dimensions
- Visible continent colors: darker fill (#d5dce3) and outline (#9aa8b8)
- Container background: #e8ecf2 for visibility even without data
- Edge click: L.DomEvent.stopPropagation, sticky tooltips
- Edge detail panel: Beijing time conversion, avg bytes per communication
- Save/restore edge layers when node highlighting
- Steps back to display:none (reliable, map inits via rAF)
Co-Authored-By: Claude <noreply@anthropic.com>
- Step visibility: use visibility:hidden instead of display:none
so map container always has dimensions for Leaflet init
- Simplify initMap: no more retry loop, always succeeds
- Node click: show related edges in panel, highlight on map
- Edge click: click edges in panel to show edge detail
- closeDetail: remove highlighted edges, restore edge toggle
- Edge rows in panel: hover effect, click to highlight+detail
Co-Authored-By: Claude <noreply@anthropic.com>
- Check container.offsetHeight before initializing Leaflet map
- Retry up to 10 times (150ms intervals) until container is visible
- Fix grid line coordinate order (lat,lon)
- Add try-catch around continent drawing
- Call invalidateSize() after initialization
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace rough SVG with Leaflet polygon continents (recognizable shapes)
- Color markers by /24 subnet hash instead of cluster IDs
- Remove count bubbles and cluster numbers from map
- Fix click handlers with stopPropagation + bubblingMouseEvents:false
- Tooltips show IP, subnet, city, country, ISP
- Legend shows community size + subnet info
Co-Authored-By: Claude <noreply@anthropic.com>
- Backend: unclusterable nodes get their own single-node cluster (never -1)
- min_cluster_size slider starts at 1 instead of 3
- Remove n_noise, all_noise, is_noise from responses and UI
- Solo clusters shown as '独立节点' with lighter color
- Remove noise toggle from legend, simplify map rendering
Co-Authored-By: Claude <noreply@anthropic.com>
- Upload result: scrollIntoView after success
- Filter preview: scrollIntoView after apply
- Cluster result: scrollIntoView after analysis
- All action buttons stay above scrollable content areas
Co-Authored-By: Claude <noreply@anthropic.com>
- quick_scan now keeps temp files for subsequent single-column streaming queries
- column_values API supports scan_id mode: streaming read of one column, low memory
- upload_csv reuses scan files via scan_id to avoid duplicate disk writes
- Frontend onFilterColChange uses scan_id when session not yet available
Co-Authored-By: Claude <noreply@anthropic.com>
- Add requirements.md documenting all changes
- Add showWarning() to base.html with .toast-warning CSS
- Add offline tile fallback message when all providers fail
- All vendor assets local, GeoIP from local file
Co-Authored-By: Claude <noreply@anthropic.com>
- Add onFilterOpChange handler on <select class=sa-filter-op>
- When operator is is_empty/not_empty: disable value select, show '— 无值 —'
- When switching back to eq/neq/contains/etc: re-enable and re-fetch values
- Fixes issue where changing operator before selecting column left value
dropdown disabled permanently
- Remove operator check from onFilterColChange (now handled separately)
Co-Authored-By: Claude <noreply@anthropic.com>
Backend:
- advanced_filter supports mode='preset' — overwrites entry['df'] so
preset filtering becomes the base data for subsequent steps
Frontend - Filter rows:
- Replace <input>+<datalist> with <select class=sa-filter-val>
- onFilterColChange populates dropdown with top 50 distinct values
sorted by count, displayed as "value (count)"
- Disables value select for is_empty/not_empty operators
Frontend - Upload preset:
- Replace JSON textarea with full condition builder (addFilterCondition/
addFilterGroup) — same format as advanced filter in Step 2
- Builder appears after upload with columns populated
- '应用预设筛选' button POSTs to filter/advanced/ with mode='preset'
- '清除预设' resets the builder
- Remove old upload-time preset_filters collection (now post-upload)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add '预设组' dropdown with two predefined templates:
1. cnrs=+ OR isrs=+ (OR group for success conditions)
2. cnam is empty AND (cnrs=+ OR isrs=+) (AND+OR nesting)
- applyFilterPreset() builds full UI tree with conditions + groups
- Presets auto-populate column, operator, and value fields
Co-Authored-By: Claude <noreply@anthropic.com>
Backend:
- Add _build_tree_condition() recursive function supporting AND/OR/NOT groups
- Modify advanced_filter to accept 'root' tree format alongside legacy flat format
- Tree supports arbitrary nesting depth: {type, logic, items}
Frontend:
- Add addFilterGroup() — creates visual ( ) group with own logic selector
- Add wrapRowInGroup() — wraps existing row into a group via [()]
- Add serializeFilterTree() — recursive JSON serialization
- Update applyAdvancedFilter to POST tree format
- Update updateFilterLogicTags for nested group labeling
- Switch filter row elements from IDs to class-based selectors
- CSS for group visual nesting (dashed left border, indentation)
Now supports: cnam='google' AND (isrs='+' OR cnrs='+')
Co-Authored-By: Claude <noreply@anthropic.com>
- Add GET /simple/column-values/ API returning distinct values for any column
(top 50 values with counts, from the uploaded DataFrame via polars)
- Update frontend onFilterColChange to fetch real data instead of hardcoded hints
- Wire datalist ID per filter row for <input list=''> binding
Co-Authored-By: Claude <noreply@anthropic.com>
- Sort column selector alphabetically (a-z) in condition builder
- Add datalist-based value suggestions per column type
(cnrs/isrs → +/-, proto → TCP/UDP/ICMP, tls_ver → 0303/0304, etc.)
- Remove redundant initFilterBuilder column population (now inline in addFilterCondition)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove CNAM frequency display, auto-selection, and related CSS from upload UI
- Remove renderCnamList, selectCnam, runFilter functions (dead code)
- Remove topCnam from SA state object
- Keep preset filter cnam field for manual pre-filtering
- Users now use Step 2 advanced filter instead
- Update Playwright test assertion
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix extra closing brace in simple_analysis.html that broke all inline JS
- Add Playwright E2E test covering upload→filter→cluster→map→edges
- Add debug support script for Playwright issue isolation
- Use small 20-row test CSV for fast test execution
- All 31 assertions pass: page load, CSV upload, advanced filter,
HDBSCAN clustering, Leaflet map, edge relationships
Co-Authored-By: Claude <noreply@anthropic.com>
- Add safe setText() helper with null check
- Make setBusy() null-safe for all DOM lookups
- Make preset filter elements optional (try/catch)
- Add null checks for all textContent assignments
- Remove emoji from error messages to avoid encoding issues
- Clarify preset filter UI labels
Co-Authored-By: Claude <noreply@anthropic.com>
Frontend improvements:
- Fixed sticky action bar with step indicator and nav buttons
- Collapsible preset filter panel with localStorage memory
- Advanced filter builder with AND/OR/NOT condition rows
- Filter builder auto-populates column names from upload
- Debug info panel (collapsible) with real-time stats
- Edge relationship layer with freq slider and toggle
- Enhanced point tooltips with IP sample and coordinate info
- Anti-duplicate button debounce via setBusy() helper
Backend:
- Add ip_sample to cluster_map for tooltip display
- Increase default cluster_selection_epsilon to 0.05
Co-Authored-By: Claude <noreply@anthropic.com>
- Download Leaflet CSS, JS, and marker images to static/vendor/
- Download Chart.js 4.4.7 to static/vendor/
- Switch template from CDN (unpkg/cdnjs) to local static assets
- Map now works fully offline without external CDN dependencies
Co-Authored-By: Claude <noreply@anthropic.com>
- Switch primary tile provider from OpenStreetMap (blocked in China)
to ArcGIS World Street Map (certified working)
- Add fallback chain: ArcGIS -> ArcGIS Gray -> OSM
- Auto-detect tile failures and fall back to next provider
- Clean up init sequence for reliable map rendering
Co-Authored-By: Claude <noreply@anthropic.com>
- Add independent 'simple_analysis' Django app with full workflow:
1. Upload/merge CSV files with auto column detection
2. Filter by cnam + cnrs + isrs (optional)
3. Clustering via HDBSCAN+haversine (geo) or /24 subnet (IP)
4. Interactive Leaflet global map with cluster-colored data points
- Integrate into nav bar, settings, and URL routing without
modifying existing analysis logic
- Cluster detail panel with IPs, top dst IPs, TLS/cipher profiles
- Chart.js bar/pie charts for cluster size distribution
- E2E Playwright tests for all workflow steps
- Support multiple CSV upload with diagonal_relaxed merge
Co-Authored-By: Claude <noreply@anthropic.com>
Add finalize_upload endpoint that starts _background_process only after
all file batches have been saved to disk. Fixes race where glob(*.csv)
captured only first batch's files.
- upload_csv: no longer starts thread, sets status='pending'
- finalize_upload: validates dir, starts processing thread
- upload.html: calls finalize after batch upload loop completes