Use slider for storage limit

This commit is contained in:
ciaranbor
2026-03-20 13:17:33 +00:00
parent 70dbc03b37
commit e6bee75d91
3 changed files with 76 additions and 21 deletions
+65 -13
View File
@@ -225,6 +225,11 @@
let configPolicy = $state<"manual" | "auto-evict">("manual");
let configSaving = $state(false);
let configApplyAll = $state(false);
let configDiskTotalGb = $derived(
storageConfigNode
? Math.round((storageConfigNode.diskTotal ?? 0) / 1024 ** 3)
: 0,
);
function openStorageConfig(col: NodeColumn) {
storageConfigNode = col;
@@ -1074,31 +1079,52 @@
<input
type="checkbox"
bind:checked={configNoLimit}
onchange={() => {
if (!configNoLimit && configMaxGb == null) {
configMaxGb = configDiskTotalGb || 50;
}
}}
class="accent-exo-yellow w-4 h-4"
/>
<span class="text-xs font-mono text-white/80">Unlimited storage</span>
</label>
<!-- Max storage input -->
<div class="space-y-1">
<label
class="text-[11px] font-mono text-white/50 uppercase tracking-wider"
for="storage-max-gb"
>
Max storage (GB)
</label>
<!-- Max storage slider -->
<div class="space-y-1.5">
<div class="flex items-baseline justify-between">
<label
class="text-[11px] font-mono text-white/50 uppercase tracking-wider"
for="storage-max-gb"
>
Max storage
</label>
<span
class="text-xs font-mono tabular-nums transition-opacity {configNoLimit
? 'opacity-30'
: 'text-white'}"
>
{configMaxGb ?? 0} GB
</span>
</div>
<input
id="storage-max-gb"
type="number"
type="range"
min="1"
max={Math.max(configDiskTotalGb, configMaxGb ?? 1)}
step="1"
min="0"
bind:value={configMaxGb}
disabled={configNoLimit}
class="w-full bg-exo-black/60 border border-exo-medium-gray/30 rounded px-3 py-1.5 text-xs font-mono text-white
focus:outline-none focus:border-exo-yellow/40
class="slider w-full h-1.5 rounded-full appearance-none cursor-pointer
disabled:opacity-30 disabled:cursor-not-allowed"
placeholder="e.g. 50"
/>
<div
class="flex justify-between text-[10px] font-mono text-white/30 transition-opacity {configNoLimit
? 'opacity-30'
: ''}"
>
<span>1 GB</span>
<span>{Math.max(configDiskTotalGb, configMaxGb ?? 1)} GB</span>
</div>
</div>
<!-- Policy selector -->
@@ -1167,4 +1193,30 @@
table {
min-width: max-content;
}
.slider {
background: rgba(255, 255, 255, 0.1);
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #f5c518;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
border: none;
background: #f5c518;
cursor: pointer;
}
.slider:disabled::-webkit-slider-thumb {
cursor: not-allowed;
}
.slider:disabled::-moz-range-thumb {
cursor: not-allowed;
}
</style>
+4 -1
View File
@@ -32,10 +32,11 @@ from exo.shared.types.memory import Memory
from exo.shared.types.profiling import MemoryUsage, NodeNetworkInfo
from exo.shared.types.tasks import Task, TaskId, TaskStatus
from exo.shared.types.worker.downloads import (
ModelModelDownloadFailed,
ModelDownloadFailed,
ModelDownloading,
ModelNotDownloading,
ModelReady,
ModelRejected,
ModelStatus,
)
from exo.shared.types.worker.instances import (
@@ -87,6 +88,8 @@ def _get_node_download_fraction(
return progress.downloaded.in_bytes / total if total > 0 else 0.0
case ModelDownloadFailed():
return 0.0
case ModelRejected():
return 0.0
return 0.0
+7 -7
View File
@@ -26,10 +26,10 @@ from exo.shared.types.tasks import TaskId, TaskStatus, TextGeneration
from exo.shared.types.text_generation import InputMessage, TextGenerationTaskParams
from exo.shared.types.topology import Connection, SocketConnection
from exo.shared.types.worker.downloads import (
DownloadCompleted,
DownloadFailed,
DownloadOngoing,
DownloadProgressData,
ModelDownloadFailed,
ModelDownloading,
ModelReady,
)
from exo.shared.types.worker.instances import (
Instance,
@@ -624,7 +624,7 @@ def test_placement_prefers_cycle_with_downloaded_model(
# node_b has the model fully downloaded, node_a does not
download_status = {
node_b: [
DownloadCompleted(
ModelReady(
node_id=node_b,
shard_metadata=shard_meta,
total=model_card.storage_size,
@@ -671,7 +671,7 @@ def test_placement_prefers_cycle_with_higher_download_progress(
# node_a: 30% downloaded, node_b: 80% downloaded
download_status = {
node_a: [
DownloadOngoing(
ModelDownloading(
node_id=node_a,
shard_metadata=shard_meta,
download_progress=DownloadProgressData(
@@ -687,7 +687,7 @@ def test_placement_prefers_cycle_with_higher_download_progress(
),
],
node_b: [
DownloadOngoing(
ModelDownloading(
node_id=node_b,
shard_metadata=shard_meta,
download_progress=DownloadProgressData(
@@ -744,7 +744,7 @@ def test_placement_does_not_prefer_cycle_with_failed_download(
# node_b has a failed download — should not be preferred
download_status = {
node_b: [
DownloadFailed(
ModelDownloadFailed(
node_id=node_b,
shard_metadata=shard_meta,
error_message="connection reset",