dashboard: redesign downloads page as model×node table#1465
dashboard: redesign downloads page as model×node table#1465AlexCheema merged 13 commits intomainfrom
Conversation
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>
30a02a7 to
867749f
Compare
ScreenshotsTable viewFull model names displayed (no truncation), with (i) info button next to each model. Subtle column separator between model and status columns. Info modalClick (i) to see model card details: size, layers, tensor parallelism, and per-node download status with color-coded badges. |
- 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>
…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>
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>
|
@JakeHillion here you go buddy, here's what it looks like with 8 nodes 🖥️ 8-Node Topology Visualization8 local exo instances connected via libp2p with |
That's not the downloads page :) |
|
Review summary (CI: all passing) Dashboard downloads page redesign (+553/-463):
Also excludes |
AlexCheema
left a comment
There was a problem hiding this comment.
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.py — model_directory field added:
class BaseDownloadProgress(TaggedModel):
node_id: NodeId
shard_metadata: ShardMetadata
model_directory: str = "" # NEW-
Default empty string is correct: Existing serialized state won't break on deserialization. The field is additive and backward-compatible.
-
_model_dir()helper incoordinator.py: UsesEXO_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. -
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
-
Data model improvement:
ModelEntry(per-node, per-model) →ModelRowwithcells: Record<string, CellStatus>creates a proper matrix. The discriminated unionCellStatuswithkindfield is clean TypeScript. -
shouldUpgradeCellwithCELL_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 ordercompleted > downloading > pending > failed > not_presentis correct. -
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. -
Utility extraction:
getDownloadTag,extractModelIdFromDownload,extractShardMetadataimported from$lib/utils/downloads— good separation of parsing logic from view code.
Observations
-
pyproject.tomlchange:--ignore=tests/start_distributed_test.pyappears 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. -
model_directoryis 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. -
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
Downloads Dashboard Screenshot (8 nodes)Requested by @JakeHillion — screenshot of the downloads table redesign with 8 local exo nodes connected via Setup: 8 exo instances on the same machine with separate |







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:...for pending downloads--for models not present on a node$lib/utils/downloadsinstead of inline re-implementationsBackend: model directory in download events
model_directoryfield toBaseDownloadProgressso all download status events include the on-disk path_model_dir()helper toDownloadCoordinatorto compute the path fromEXO_MODELS_DIRInfo modal
Other fixes
tests/start_distributed_test.pyfrom pytest collection (CLI script that callssys.exit()at import time)Test Plan
uv run basedpyright— 0 errorsuv run ruff check— all passednix fmt— cleanuv run pytest— 188 passed, 1 skipped🤖 Generated with Claude Code