Skip to content

Commit bd10c19

Browse files
[CLI] Add color to the STATUS column
1 parent de0ae07 commit bd10c19

File tree

3 files changed

+379
-7
lines changed

3 files changed

+379
-7
lines changed

src/dstack/_internal/cli/utils/run.py

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Probe,
1717
ProbeSpec,
1818
RunPlan,
19+
RunStatus,
1920
)
2021
from dstack._internal.core.services.profiles import get_termination
2122
from dstack._internal.utils.common import (
@@ -182,6 +183,54 @@ def th(s: str) -> str:
182183
console.print(NO_OFFERS_WARNING)
183184

184185

186+
def _get_run_status_style(status: RunStatus, status_message: Optional[str] = None) -> str:
187+
color_map = {
188+
RunStatus.PENDING: "white",
189+
RunStatus.SUBMITTED: "grey",
190+
RunStatus.PROVISIONING: "deep_sky_blue1",
191+
RunStatus.RUNNING: "sea_green3",
192+
RunStatus.TERMINATING: "white",
193+
RunStatus.TERMINATED: "grey",
194+
RunStatus.FAILED: "indian_red1",
195+
RunStatus.DONE: "grey",
196+
}
197+
198+
if status_message == "no offers" or status_message == "interrupted":
199+
color = "gold1"
200+
elif status_message == "pulling":
201+
color = "sea_green3"
202+
else:
203+
color = color_map.get(status, "white")
204+
205+
if not status.is_finished():
206+
return f"bold {color}"
207+
return color
208+
209+
210+
def _get_job_status_style(status_message: str, job_status: JobStatus) -> str:
211+
if status_message in ("no offers", "interrupted"):
212+
color = "gold1"
213+
elif status_message == "stopped":
214+
color = "grey"
215+
else:
216+
color_map = {
217+
JobStatus.SUBMITTED: "grey",
218+
JobStatus.PROVISIONING: "deep_sky_blue1",
219+
JobStatus.PULLING: "sea_green3",
220+
JobStatus.RUNNING: "sea_green3",
221+
JobStatus.TERMINATING: "white",
222+
JobStatus.TERMINATED: "grey",
223+
JobStatus.ABORTED: "gold1",
224+
JobStatus.FAILED: "indian_red1",
225+
JobStatus.DONE: "grey",
226+
}
227+
color = color_map.get(job_status, "white")
228+
229+
if not job_status.is_finished():
230+
return f"bold {color}"
231+
return color
232+
233+
185234
def get_runs_table(
186235
runs: List[Run], verbose: bool = False, format_date: DateFormatter = pretty_date
187236
) -> Table:
@@ -212,15 +261,20 @@ def get_runs_table(
212261
)
213262
merge_job_rows = len(run.jobs) == 1 and not show_deployment_num
214263

264+
status_text = (
265+
run.latest_job_submission.status_message
266+
if run.status.is_finished() and run.latest_job_submission
267+
else run.status_message
268+
)
269+
status_style = _get_run_status_style(
270+
run.status, status_message=status_text if run.status.is_finished() else status_text
271+
)
272+
215273
run_row: Dict[Union[str, int], Any] = {
216274
"NAME": run.run_spec.run_name
217275
+ (f" [secondary]deployment={run.deployment_num}[/]" if show_deployment_num else ""),
218276
"SUBMITTED": format_date(run.submitted_at),
219-
"STATUS": (
220-
run.latest_job_submission.status_message
221-
if run.status.is_finished() and run.latest_job_submission
222-
else run.status_message
223-
),
277+
"STATUS": f"[{status_style}]{status_text}[/]",
224278
}
225279
if run.error:
226280
run_row["ERROR"] = run.error
@@ -233,14 +287,17 @@ def get_runs_table(
233287
if verbose and latest_job_submission.inactivity_secs:
234288
inactive_for = format_duration_multiunit(latest_job_submission.inactivity_secs)
235289
status += f" (inactive for {inactive_for})"
290+
status_text = latest_job_submission.status_message
291+
status_style = _get_job_status_style(status_text, latest_job_submission.status)
292+
236293
job_row: Dict[Union[str, int], Any] = {
237294
"NAME": f" replica={job.job_spec.replica_num} job={job.job_spec.job_num}"
238295
+ (
239296
f" deployment={latest_job_submission.deployment_num}"
240297
if show_deployment_num
241298
else ""
242299
),
243-
"STATUS": latest_job_submission.status_message,
300+
"STATUS": f"[{status_style}]{status_text}[/]",
244301
"PROBES": _format_job_probes(
245302
job.job_spec.probes, latest_job_submission.probes, latest_job_submission.status
246303
),
@@ -267,8 +324,8 @@ def get_runs_table(
267324
}
268325
)
269326
if merge_job_rows:
270-
# merge rows
271327
job_row.update(run_row)
328+
job_row["STATUS"] = run_row["STATUS"]
272329
add_row_from_dict(table, job_row, style="secondary" if len(run.jobs) != 1 else None)
273330

274331
return table
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from unittest.mock import Mock
2+
3+
import pytest
4+
5+
from dstack._internal.server.services.docker import ImageConfig, ImageConfigObject
6+
7+
8+
@pytest.fixture
9+
def image_config_mock(monkeypatch: pytest.MonkeyPatch) -> ImageConfig:
10+
image_config = ImageConfig.parse_obj({"User": None, "Entrypoint": None, "Cmd": ["/bin/bash"]})
11+
monkeypatch.setattr(
12+
"dstack._internal.server.services.jobs.configurators.base._get_image_config",
13+
Mock(return_value=image_config),
14+
)
15+
monkeypatch.setattr(
16+
"dstack._internal.server.services.docker.get_image_config",
17+
Mock(return_value=ImageConfigObject(config=image_config)),
18+
)
19+
return image_config

0 commit comments

Comments
 (0)