Skip to content

dashboard: redesign downloads page as model×node table#1465

Merged
AlexCheema merged 13 commits intomainfrom
alexcheema/downloads-table-redesign
Feb 17, 2026
Merged

dashboard: redesign downloads page as model×node table#1465
AlexCheema merged 13 commits intomainfrom
alexcheema/downloads-table-redesign

Conversation

@AlexCheema
Copy link
Copy Markdown
Contributor

@AlexCheema AlexCheema commented Feb 12, 2026

Motivation

The current downloads page uses a node-centric card grid layout that is messy and hard to read — the same model across different nodes appears in separate cards, and deep nesting wastes space. This makes it difficult to quickly see which models are on which nodes.

Changes

Rewrote the downloads page (dashboard/src/routes/downloads/+page.svelte) from a card grid to a clean table layout:

  • Rows = models (unique across all nodes)
  • Columns = nodes (with disk free shown in header)
  • Cells show status at a glance:
    • ✅ Green checkmark + size for completed downloads
    • 🟡 Yellow percentage + mini progress bar + speed for active downloads
    • ... for pending downloads
    • ❌ Red X for failed downloads
    • -- for models not present on a node
  • Delete/download action buttons appear on row hover
  • Model name column is sticky on horizontal scroll (for many-node clusters)
  • Models sorted by number of nodes with completed downloads
  • Imported shared utilities from $lib/utils/downloads instead of inline re-implementations

Backend: model directory in download events

  • Added model_directory field to BaseDownloadProgress so all download status events include the on-disk path
  • Added _model_dir() helper to DownloadCoordinator to compute the path from EXO_MODELS_DIR
  • Dashboard uses this to show file location and enable "open in Finder" for completed downloads

Info modal

  • Clicking a model name opens an info modal showing card details (family, quantization, capabilities, storage size, layer count, tensor parallelism support)

Other fixes

  • Fixed model name truncation in the table
  • Excluded tests/start_distributed_test.py from pytest collection (CLI script that calls sys.exit() at import time)

Test Plan

  • uv run basedpyright — 0 errors
  • uv run ruff check — all passed
  • nix fmt — clean
  • uv run pytest — 188 passed, 1 skipped

🤖 Generated with Claude Code

AlexCheema and others added 2 commits February 12, 2026 13:15
Replace the node-centric card grid layout with a clean table where rows
are models and columns are nodes. Each cell shows a green checkmark
(completed), yellow percentage with progress bar (downloading), or gray
dash (not present). Actions (delete/download) appear on row hover.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Models downloaded on more nodes appear first in the table, with
alphabetical as tiebreaker. Pending/failed entries don't count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AlexCheema AlexCheema force-pushed the alexcheema/downloads-table-redesign branch from 30a02a7 to 867749f Compare February 12, 2026 22:18
@AlexCheema
Copy link
Copy Markdown
Contributor Author

AlexCheema commented Feb 12, 2026

Screenshots

Table view

Full model names displayed (no truncation), with (i) info button next to each model. Subtle column separator between model and status columns.

Downloads table

Info modal

Click (i) to see model card details: size, layers, tensor parallelism, and per-node download status with color-coded badges.

Info modal

- Remove hardcoded max-w-[280px] truncation so full model names display
- Add (i) button next to each model that opens a detail modal showing
  model card info (family, quantization, size, layers, capabilities,
  tensor parallelism support, per-node download status)
- Add subtle column separator border between model and node columns
- Add align-middle to status cells for consistent vertical alignment
- Info modal follows the same pattern as ModelPickerModal (backdrop +
  fly transition + key-value layout)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AlexCheema
Copy link
Copy Markdown
Contributor Author

2-Node Cluster View

Screenshot showing the downloads table with 2 nodes in the cluster (same machine, separate EXO_HOME dirs):

2-Node Downloads Table

AlexCheema and others added 2 commits February 12, 2026 15:44
…loads table

Make the delete button always visible on completed download cells instead
of only on hover. Show the on-disk model directory path per node in the
info modal by adding model_directory to download progress state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AlexCheema
Copy link
Copy Markdown
Contributor Author

Screenshots: Delete button + file location

Downloads table — delete button always visible

Downloads table

Info modal — shows on-disk file path per node

Info modal

AlexCheema and others added 4 commits February 12, 2026 16:15
nix fmt reformatted long lines in coordinator.py and +page.svelte.
Added --ignore for tests/start_distributed_test.py which is a CLI
script (not a test module) that calls sys.exit() at import time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AlexCheema AlexCheema enabled auto-merge (squash) February 15, 2026 22:03
@AlexCheema AlexCheema requested a review from Evanev7 February 16, 2026 12:58
@AlexCheema
Copy link
Copy Markdown
Contributor Author

@JakeHillion here you go buddy, here's what it looks like with 8 nodes 🖥️

8-Node Topology Visualization

8-node topology

8 local exo instances connected via libp2p with EXO_LIBP2P_NAMESPACE isolation. All nodes discovered each other and formed the full mesh topology.

@JakeHillion
Copy link
Copy Markdown
Contributor

@JakeHillion here you go buddy, here's what it looks like with 8 nodes 🖥️

That's not the downloads page :)

@AlexCheema
Copy link
Copy Markdown
Contributor Author

Review summary (CI: all passing)

Dashboard downloads page redesign (+553/-463):

  • Layout: Replaces node-centric card grid with a model×node table — rows are models, columns are nodes with disk free in headers
  • Status cells: green checkmark (completed), yellow % with progress bar (downloading), red X (failed), dash (not present)
  • Sorting: Models with more completed downloads across nodes appear first
  • Info modal: Click model name to see card details (family, quantization, capabilities, layers, tensor support)
  • Backend: Adds model_directory field to BaseDownloadProgress so download events include on-disk path
  • UX: Sticky model name column for horizontal scrolling, hover-reveal delete/download actions

Also excludes start_distributed_test.py from pytest collection. Clean refactor of the downloads view with a small backend addition for file paths.

Copy link
Copy Markdown
Contributor Author

@AlexCheema AlexCheema left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #1465 — Dashboard downloads page redesign as model×node table

Overall Assessment

Well-executed redesign that transforms the downloads page from a per-node list into a model×node matrix. The backend changes are clean and the new data model is more informative. Good separation of concerns with utility extraction.

Backend Changes

downloads.pymodel_directory field added:

class BaseDownloadProgress(TaggedModel):
    node_id: NodeId
    shard_metadata: ShardMetadata
    model_directory: str = ""  # NEW
  1. Default empty string is correct: Existing serialized state won't break on deserialization. The field is additive and backward-compatible.

  2. _model_dir() helper in coordinator.py: Uses EXO_MODELS_DIR / model_id.normalize() to compute the directory path. This is consistent with how the downloader resolves paths. The method is called in all 6 download status emission points (pending, ongoing, completed, failed, existing progress). Good coverage.

  3. No path traversal risk: model_id.normalize() sanitizes the model ID. The path is only used for display in the dashboard, not for file operations in this PR.

Dashboard Changes

  1. Data model improvement: ModelEntry (per-node, per-model) → ModelRow with cells: Record<string, CellStatus> creates a proper matrix. The discriminated union CellStatus with kind field is clean TypeScript.

  2. shouldUpgradeCell with CELL_PRIORITY: When multiple download entries exist for the same model on the same node (e.g., pending→downloading transitions), the higher-priority status wins. Priority order completed > downloading > pending > failed > not_present is correct.

  3. extractModelCard: Extracts model metadata for display (family, quantization, base model, capabilities). The snake_case/camelCase dual-access pattern (meta.base_model ?? meta.baseModel) handles both serialization formats correctly.

  4. Utility extraction: getDownloadTag, extractModelIdFromDownload, extractShardMetadata imported from $lib/utils/downloads — good separation of parsing logic from view code.

Observations

  1. pyproject.toml change: --ignore=tests/start_distributed_test.py appears in this PR too (same as #1466 and #1478). This should be coordinated — whichever PR merges first will include it, and the others will have a merge conflict on this line. Consider putting this in its own PR or ensuring only one PR carries it.

  2. model_directory is exposed to dashboard but not sanitized for display: The full filesystem path (e.g., /Users/gary/.cache/exo/models/mlx-community/Qwen3-30B-A3B-4bit) is sent to the browser. This is fine for a local-first application but would be an information disclosure concern in a hosted scenario. Not actionable for exo's use case.

  3. No delete confirmation in the new UI: Verify the new table design still has delete functionality with confirmation. The old design had per-model delete buttons — ensure the redesign preserves this.

Verdict

Approve. Clean backend addition (model_directory field) with correct default. Dashboard redesign is well-typed with proper state priority handling. The pyproject.toml change should be coordinated across the 3 PRs that include it.

🤖 Generated with Claude Code

@AlexCheema
Copy link
Copy Markdown
Contributor Author

AlexCheema commented Feb 17, 2026

Downloads Dashboard Screenshot (8 nodes)

Requested by @JakeHillion — screenshot of the downloads table redesign with 8 local exo nodes connected via EXO_LIBP2P_NAMESPACE=screenshot-test.

Downloads Dashboard - 8 Nodes

Setup: 8 exo instances on the same machine with separate EXO_HOME dirs, shared namespace for peer discovery. All 8 nodes visible as columns with 48 model entries showing pending download status across the cluster. Dashboard rendered at 1920x1080 with zoomed-out view to show all columns.

Copy link
Copy Markdown
Member

@Evanev7 Evanev7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

@AlexCheema AlexCheema merged commit d6301ed into main Feb 17, 2026
6 checks passed
@AlexCheema AlexCheema deleted the alexcheema/downloads-table-redesign branch February 17, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants