cloudos-cli 2.89.0__tar.gz → 2.89.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/PKG-INFO +12 -10
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/README.md +11 -9
- cloudos_cli-2.89.1/cloudos_cli/_version.py +1 -0
- cloudos_cli-2.89.1/cloudos_cli/constants.py +75 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/details.py +189 -146
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli.egg-info/PKG-INFO +12 -10
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli.egg-info/SOURCES.txt +1 -0
- cloudos_cli-2.89.1/tests/test_details.py +444 -0
- cloudos_cli-2.89.0/cloudos_cli/_version.py +0 -1
- cloudos_cli-2.89.0/cloudos_cli/constants.py +0 -28
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/LICENSE +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/__main__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/bash/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/bash/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/clos.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/configure/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/configure/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/configure/configure.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/cost/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/cost/cost.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/cromwell/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/cromwell/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/datasets/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/datasets/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/datasets/datasets.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/import_wf/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/import_wf/import_wf.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/interactive_session/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/interactive_session/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/interactive_session/interactive_session.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/jobs/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/jobs/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/jobs/job.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/link/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/link/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/link/link.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/logging/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/logging/logger.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/procurement/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/procurement/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/procurement/images.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/projects/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/projects/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/queue/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/queue/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/queue/queue.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/related_analyses/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/related_analyses/related_analyses.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/array_job.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/cli_helpers.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/cloud.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/errors.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/last_wf.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/nextflow_version.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/requests.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/utils/resources.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/workflows/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli/workflows/cli.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli.egg-info/dependency_links.txt +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli.egg-info/entry_points.txt +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli.egg-info/requires.txt +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/cloudos_cli.egg-info/top_level.txt +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/setup.cfg +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/setup.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/functions_for_pytest.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_cli_project_create.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_cost/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_cost/test_job_cost.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_error_messages.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_interactive_session/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_interactive_session/test_create_session.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_interactive_session/test_list_sessions.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_logging/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_logging/test_logger.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_nextflow_version.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_related_analyses/__init__.py +0 -0
- {cloudos_cli-2.89.0 → cloudos_cli-2.89.1}/tests/test_related_analyses/test_related_analyses.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.89.
|
|
3
|
+
Version: 2.89.1
|
|
4
4
|
Summary: Python package for interacting with CloudOS
|
|
5
5
|
Home-page: https://github.com/lifebit-ai/cloudos-cli
|
|
6
6
|
Author: David Piñeyro
|
|
@@ -799,19 +799,21 @@ The output shows a rich table with job information and pagination details:
|
|
|
799
799
|
```console
|
|
800
800
|
Executing list...
|
|
801
801
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
│
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
└────────┴──────────────┴─────────────┴──────────┴──────────────┴─────────────────────────┴──────────────┘
|
|
802
|
+
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
|
|
803
|
+
┃ Status ┃ ID ┃ Pipeline ┃ Name ┃ Project ┃ Owner ┃ Runtime ┃
|
|
804
|
+
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
|
|
805
|
+
│ ✓ │ 692ee71c40e98ed6ed529e43 │ rnatoy │ analysis_run │ test-proj │ John Doe │ 15m 30s │
|
|
806
|
+
│ ◐ │ 692ee81d50f98ed7fe639f54 │ VEP │ test_job │ research │ Jane Sm… │ 2m 15s │
|
|
807
|
+
└────────┴──────────────────────────┴──────────────┴────────────────┴──────────────┴──────────┴─────────┘
|
|
808
|
+
|
|
809
|
+
Legend: ✓ = Completed | ◐ = Running | ✗ = Failed | ■ = Aborted | ○ = Initialising | ? = Unknown
|
|
811
810
|
|
|
812
811
|
Showing 10 of 45 total jobs | Page 1 of 5
|
|
813
812
|
```
|
|
814
813
|
|
|
814
|
+
> [!NOTE]
|
|
815
|
+
> **Responsive Table Display**: The table automatically adapts to your terminal width, intelligently selecting which columns to display. Narrow terminals show essential columns (Status, ID, Pipeline, Name), while wider terminals progressively add more information (Project, Owner, Runtime, Cost, timestamps, etc.). The table ensures only complete columns are shown and always renders with proper borders.
|
|
816
|
+
|
|
815
817
|
**Status Indicators**
|
|
816
818
|
|
|
817
819
|
Jobs are displayed with colored visual status indicators:
|
|
@@ -764,19 +764,21 @@ The output shows a rich table with job information and pagination details:
|
|
|
764
764
|
```console
|
|
765
765
|
Executing list...
|
|
766
766
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
│
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
└────────┴──────────────┴─────────────┴──────────┴──────────────┴─────────────────────────┴──────────────┘
|
|
767
|
+
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
|
|
768
|
+
┃ Status ┃ ID ┃ Pipeline ┃ Name ┃ Project ┃ Owner ┃ Runtime ┃
|
|
769
|
+
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
|
|
770
|
+
│ ✓ │ 692ee71c40e98ed6ed529e43 │ rnatoy │ analysis_run │ test-proj │ John Doe │ 15m 30s │
|
|
771
|
+
│ ◐ │ 692ee81d50f98ed7fe639f54 │ VEP │ test_job │ research │ Jane Sm… │ 2m 15s │
|
|
772
|
+
└────────┴──────────────────────────┴──────────────┴────────────────┴──────────────┴──────────┴─────────┘
|
|
773
|
+
|
|
774
|
+
Legend: ✓ = Completed | ◐ = Running | ✗ = Failed | ■ = Aborted | ○ = Initialising | ? = Unknown
|
|
776
775
|
|
|
777
776
|
Showing 10 of 45 total jobs | Page 1 of 5
|
|
778
777
|
```
|
|
779
778
|
|
|
779
|
+
> [!NOTE]
|
|
780
|
+
> **Responsive Table Display**: The table automatically adapts to your terminal width, intelligently selecting which columns to display. Narrow terminals show essential columns (Status, ID, Pipeline, Name), while wider terminals progressively add more information (Project, Owner, Runtime, Cost, timestamps, etc.). The table ensures only complete columns are shown and always renders with proper borders.
|
|
781
|
+
|
|
780
782
|
**Status Indicators**
|
|
781
783
|
|
|
782
784
|
Jobs are displayed with colored visual status indicators:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.89.1'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Global constants for CloudOS CLI."""
|
|
2
|
+
|
|
3
|
+
# Job status constants
|
|
4
|
+
JOB_COMPLETED = 'completed'
|
|
5
|
+
JOB_FAILED = 'failed'
|
|
6
|
+
JOB_ABORTED = 'aborted'
|
|
7
|
+
|
|
8
|
+
# Nextflow version constants
|
|
9
|
+
AWS_NEXTFLOW_VERSIONS = ['22.10.8', '24.04.4', '25.04.8', '25.10.4']
|
|
10
|
+
AZURE_NEXTFLOW_VERSIONS = ['22.11.1-edge']
|
|
11
|
+
HPC_NEXTFLOW_VERSIONS = ['22.10.8']
|
|
12
|
+
AWS_NEXTFLOW_LATEST = '25.10.4'
|
|
13
|
+
AZURE_NEXTFLOW_LATEST = '22.11.1-edge'
|
|
14
|
+
HPC_NEXTFLOW_LATEST = '22.10.8'
|
|
15
|
+
|
|
16
|
+
# Nextflow version defaults by workflow type
|
|
17
|
+
PLATFORM_WORKFLOW_NEXTFLOW_VERSION = '22.10.8' # For Lifebit Platform workflows (modules)
|
|
18
|
+
USER_WORKFLOW_NEXTFLOW_VERSION = '24.04.4' # For user-imported workflows
|
|
19
|
+
|
|
20
|
+
# Job abort states
|
|
21
|
+
ABORT_JOB_STATES = ['running', 'initializing']
|
|
22
|
+
|
|
23
|
+
# Request interval for Cromwell
|
|
24
|
+
REQUEST_INTERVAL_CROMWELL = 30
|
|
25
|
+
|
|
26
|
+
# Global constants for CloudOS CLI
|
|
27
|
+
CLOUDOS_URL = 'https://cloudos.lifebit.ai'
|
|
28
|
+
INIT_PROFILE = 'initialisingProfile'
|
|
29
|
+
|
|
30
|
+
# Job status symbol mapping
|
|
31
|
+
JOB_STATUS_SYMBOLS = {
|
|
32
|
+
"completed": "[bold green]✓[/bold green]",
|
|
33
|
+
"running": "[bold bright_black]◐[/bold bright_black]",
|
|
34
|
+
"failed": "[bold red]✗[/bold red]",
|
|
35
|
+
"aborted": "[bold orange3]■[/bold orange3]",
|
|
36
|
+
"aborting": "[bold orange3]⊡[/bold orange3]",
|
|
37
|
+
"initialising": "[bold bright_black]○[/bold bright_black]",
|
|
38
|
+
"scheduled": "[bold cyan]◷[/bold cyan]",
|
|
39
|
+
"n/a": "[bold bright_black]?[/bold bright_black]"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Column priority groups for responsive table display
|
|
43
|
+
COLUMN_PRIORITY_GROUPS = {
|
|
44
|
+
'minimal': ['status', 'id', 'name'],
|
|
45
|
+
'essential': ['status', 'id', 'name', 'pipeline'],
|
|
46
|
+
'important': ['project', 'owner', 'run_time', 'cost'],
|
|
47
|
+
'useful': ['submit_time', 'end_time', 'commit'],
|
|
48
|
+
'extended': ['resources', 'storage_type']
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Essential columns priority order for auto-selection
|
|
52
|
+
ESSENTIAL_COLUMN_PRIORITY = ['status', 'id', 'name', 'pipeline']
|
|
53
|
+
|
|
54
|
+
# Additional columns priority order for auto-selection
|
|
55
|
+
ADDITIONAL_COLUMN_PRIORITY = [
|
|
56
|
+
'project', 'owner', 'run_time', 'cost',
|
|
57
|
+
'submit_time', 'end_time', 'commit', 'resources', 'storage_type'
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# Column configurations for job list table
|
|
61
|
+
COLUMN_CONFIGS = {
|
|
62
|
+
'status': {"header": "Status", "style": "cyan", "no_wrap": True, "min_width": 6, "max_width": 6},
|
|
63
|
+
'name': {"header": "Name", "style": "green", "overflow": "fold", "no_wrap": False, "min_width": 6, "max_width": 14},
|
|
64
|
+
'project': {"header": "Project", "style": "magenta", "overflow": "fold", "no_wrap": False, "min_width": 6, "max_width": 18},
|
|
65
|
+
'owner': {"header": "Owner", "style": "blue", "overflow": "fold", "no_wrap": False, "min_width": 4, "max_width": 14},
|
|
66
|
+
'pipeline': {"header": "Pipeline", "style": "yellow", "overflow": "fold", "no_wrap": False, "min_width": 8, "max_width": 14},
|
|
67
|
+
'id': {"header": "ID", "style": "white", "overflow": "ellipsis", "no_wrap": True, "min_width": 24, "max_width": 24},
|
|
68
|
+
'submit_time': {"header": "Submit", "style": "cyan", "no_wrap": True, "min_width": 12, "max_width": 16},
|
|
69
|
+
'end_time': {"header": "End", "style": "cyan", "no_wrap": True, "min_width": 12, "max_width": 16},
|
|
70
|
+
'run_time': {"header": "Runtime", "style": "green", "no_wrap": True, "min_width": 8, "max_width": 12},
|
|
71
|
+
'commit': {"header": "Commit", "style": "magenta", "no_wrap": True, "min_width": 9, "max_width": 10},
|
|
72
|
+
'cost': {"header": "Cost", "style": "yellow", "no_wrap": True, "min_width": 8, "max_width": 12},
|
|
73
|
+
'resources': {"header": "Resources", "style": "blue", "overflow": "ellipsis", "no_wrap": True, "min_width": 8, "max_width": 16},
|
|
74
|
+
'storage_type': {"header": "Storage", "style": "white", "no_wrap": True, "min_width": 8, "max_width": 10}
|
|
75
|
+
}
|
|
@@ -6,6 +6,14 @@ import csv
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
+
from cloudos_cli.constants import (
|
|
10
|
+
JOB_STATUS_SYMBOLS,
|
|
11
|
+
COLUMN_PRIORITY_GROUPS,
|
|
12
|
+
ESSENTIAL_COLUMN_PRIORITY,
|
|
13
|
+
ADDITIONAL_COLUMN_PRIORITY,
|
|
14
|
+
COLUMN_CONFIGS
|
|
15
|
+
)
|
|
16
|
+
|
|
9
17
|
|
|
10
18
|
def get_path(param, param_kind_map, execution_platform, storage_provider, mode="parameters"):
|
|
11
19
|
"""
|
|
@@ -362,15 +370,7 @@ def _build_job_row_values(job, cloudos_url, terminal_width, columns_to_show):
|
|
|
362
370
|
"""
|
|
363
371
|
# Status with colored and bold ANSI symbols
|
|
364
372
|
status_raw = str(job.get("status", "N/A"))
|
|
365
|
-
|
|
366
|
-
"completed": "[bold green]✓[/bold green]",
|
|
367
|
-
"running": "[bold bright_black]◐[/bold bright_black]",
|
|
368
|
-
"failed": "[bold red]✗[/bold red]",
|
|
369
|
-
"aborted": "[bold orange3]■[/bold orange3]",
|
|
370
|
-
"initialising": "[bold bright_black]○[/bold bright_black]",
|
|
371
|
-
"N/A": "[bold bright_black]?[/bold bright_black]"
|
|
372
|
-
}
|
|
373
|
-
status = status_symbol_map.get(status_raw.lower(), status_raw)
|
|
373
|
+
status = JOB_STATUS_SYMBOLS.get(status_raw.lower(), status_raw)
|
|
374
374
|
|
|
375
375
|
# Name
|
|
376
376
|
name = str(job.get("name", "N/A"))
|
|
@@ -378,24 +378,16 @@ def _build_job_row_values(job, cloudos_url, terminal_width, columns_to_show):
|
|
|
378
378
|
# Project
|
|
379
379
|
project = str(job.get("project", {}).get("name", "N/A"))
|
|
380
380
|
|
|
381
|
-
# Owner (
|
|
381
|
+
# Owner (single-line format, no wrapping)
|
|
382
382
|
user_info = job.get("user", {})
|
|
383
383
|
name_part = user_info.get('name', '')
|
|
384
384
|
surname_part = user_info.get('surname', '')
|
|
385
|
-
if
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
owner = (name_part or surname_part)[:8]
|
|
390
|
-
else:
|
|
391
|
-
owner = "N/A"
|
|
385
|
+
if name_part and surname_part:
|
|
386
|
+
owner = f"{name_part} {surname_part}"
|
|
387
|
+
elif name_part or surname_part:
|
|
388
|
+
owner = name_part or surname_part
|
|
392
389
|
else:
|
|
393
|
-
|
|
394
|
-
owner = f"{name_part}\n{surname_part}"
|
|
395
|
-
elif name_part or surname_part:
|
|
396
|
-
owner = name_part or surname_part
|
|
397
|
-
else:
|
|
398
|
-
owner = "N/A"
|
|
390
|
+
owner = "N/A"
|
|
399
391
|
|
|
400
392
|
# Pipeline
|
|
401
393
|
pipeline = str(job.get("workflow", {}).get("name", "N/A")).split('\n')[0].strip()
|
|
@@ -407,23 +399,23 @@ def _build_job_row_values(job, cloudos_url, terminal_width, columns_to_show):
|
|
|
407
399
|
job_url = f"{cloudos_url}/app/advanced-analytics/analyses/{job_id}"
|
|
408
400
|
job_id_with_link = f"[link={job_url}]{job_id}[/link]"
|
|
409
401
|
|
|
410
|
-
# Submit time (
|
|
402
|
+
# Submit time (single-line format)
|
|
411
403
|
created_at = job.get("createdAt")
|
|
412
404
|
if created_at:
|
|
413
405
|
try:
|
|
414
406
|
dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
|
|
415
|
-
submit_time = dt.strftime('%m-%d
|
|
407
|
+
submit_time = dt.strftime('%m-%d %H:%M') if terminal_width < 90 else dt.strftime('%Y-%m-%d %H:%M')
|
|
416
408
|
except (ValueError, TypeError):
|
|
417
409
|
submit_time = "N/A"
|
|
418
410
|
else:
|
|
419
411
|
submit_time = "N/A"
|
|
420
412
|
|
|
421
|
-
# End time (
|
|
413
|
+
# End time (single-line format)
|
|
422
414
|
end_time_raw = job.get("endTime")
|
|
423
415
|
if end_time_raw:
|
|
424
416
|
try:
|
|
425
417
|
dt = datetime.fromisoformat(end_time_raw.replace('Z', '+00:00'))
|
|
426
|
-
end_time = dt.strftime('%m-%d
|
|
418
|
+
end_time = dt.strftime('%m-%d %H:%M') if terminal_width < 90 else dt.strftime('%Y-%m-%d %H:%M')
|
|
427
419
|
except (ValueError, TypeError):
|
|
428
420
|
end_time = "N/A"
|
|
429
421
|
else:
|
|
@@ -504,7 +496,28 @@ def _build_job_row_values(job, cloudos_url, terminal_width, columns_to_show):
|
|
|
504
496
|
return [column_values[col] for col in columns_to_show]
|
|
505
497
|
|
|
506
498
|
|
|
507
|
-
def
|
|
499
|
+
def _create_status_legend():
|
|
500
|
+
"""Create a formatted legend for job status symbols.
|
|
501
|
+
|
|
502
|
+
Returns
|
|
503
|
+
-------
|
|
504
|
+
str
|
|
505
|
+
Formatted legend string with status symbols and their meanings.
|
|
506
|
+
"""
|
|
507
|
+
legend_items = [
|
|
508
|
+
"[bold cyan]◷[/bold cyan] = Scheduled",
|
|
509
|
+
"[bold bright_black]○[/bold bright_black] = Initialising",
|
|
510
|
+
"[bold bright_black]◐[/bold bright_black] = Running",
|
|
511
|
+
"[bold green]✓[/bold green] = Completed",
|
|
512
|
+
"[bold red]✗[/bold red] = Failed",
|
|
513
|
+
"[bold orange3]⊡[/bold orange3] = Aborting",
|
|
514
|
+
"[bold orange3]■[/bold orange3] = Aborted",
|
|
515
|
+
"[bold bright_black]?[/bold bright_black] = Unknown"
|
|
516
|
+
]
|
|
517
|
+
return "[cyan]Legend:[/cyan] " + " | ".join(legend_items)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, column_configs):
|
|
508
521
|
"""Helper function to build a complete job table.
|
|
509
522
|
|
|
510
523
|
Parameters
|
|
@@ -517,7 +530,7 @@ def _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, all_col
|
|
|
517
530
|
Current terminal width for responsive formatting
|
|
518
531
|
columns_to_show : list
|
|
519
532
|
List of column keys to include
|
|
520
|
-
|
|
533
|
+
column_configs : dict
|
|
521
534
|
Dictionary of all column configurations
|
|
522
535
|
|
|
523
536
|
Returns
|
|
@@ -525,11 +538,11 @@ def _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, all_col
|
|
|
525
538
|
Table
|
|
526
539
|
Rich Table object populated with job rows
|
|
527
540
|
"""
|
|
528
|
-
table = Table(
|
|
541
|
+
table = Table()
|
|
529
542
|
|
|
530
543
|
# Add columns to table
|
|
531
544
|
for col_key in columns_to_show:
|
|
532
|
-
col_config =
|
|
545
|
+
col_config = column_configs[col_key]
|
|
533
546
|
table.add_column(
|
|
534
547
|
col_config["header"],
|
|
535
548
|
style=col_config.get("style"),
|
|
@@ -547,129 +560,169 @@ def _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, all_col
|
|
|
547
560
|
return table
|
|
548
561
|
|
|
549
562
|
|
|
550
|
-
def
|
|
551
|
-
"""
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
- Wide (≥120 chars): + Extended columns (resources, storage_type)
|
|
559
|
-
|
|
560
|
-
Status symbols are displayed with colors:
|
|
561
|
-
- Green ✓ for completed jobs
|
|
562
|
-
- Grey ◐ for running jobs
|
|
563
|
-
- Red ✗ for failed jobs
|
|
564
|
-
- Orange ■ for aborted jobs
|
|
565
|
-
- Grey ○ for initialising jobs
|
|
566
|
-
- Grey ? for unknown status
|
|
563
|
+
def _calculate_table_width(column_list, col_configs):
|
|
564
|
+
"""Calculate total table width including all overhead."""
|
|
565
|
+
borders_and_separators = 2 + (len(column_list) - 1)
|
|
566
|
+
column_widths = sum(
|
|
567
|
+
col_configs[col].get('max_width', col_configs[col].get('min_width', 10)) + 2
|
|
568
|
+
for col in column_list
|
|
569
|
+
)
|
|
570
|
+
buffer = 2
|
|
567
571
|
|
|
572
|
+
return borders_and_separators + column_widths + buffer
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _fit_columns_to_terminal(cols, terminal_w, col_configs, preserve_order=False):
|
|
576
|
+
"""Build column list progressively, only adding columns that fit completely.
|
|
577
|
+
|
|
568
578
|
Parameters
|
|
569
579
|
----------
|
|
570
|
-
|
|
571
|
-
List of
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
selected_columns : str or list, optional
|
|
580
|
-
Column names to display. Can be:
|
|
581
|
-
- None: Auto-responsive based on terminal width
|
|
582
|
-
- String: Comma-separated column names (e.g., "status,name,cost")
|
|
583
|
-
- List: List of column names
|
|
584
|
-
Valid columns: 'status', 'name', 'project', 'owner', 'pipeline', 'id',
|
|
585
|
-
'submit_time', 'end_time', 'run_time', 'commit', 'cost', 'resources', 'storage_type'
|
|
586
|
-
fetch_page_callback : callable, optional
|
|
587
|
-
Callback function to fetch a specific page of results for interactive pagination.
|
|
588
|
-
Should accept page number (1-indexed) and return dict with 'jobs' and 'pagination_metadata' keys.
|
|
589
|
-
If provided, enables interactive navigation (n=next, p=previous, q=quit).
|
|
590
|
-
|
|
580
|
+
cols : list
|
|
581
|
+
List of column keys to fit
|
|
582
|
+
terminal_w : int
|
|
583
|
+
Terminal width to fit columns into
|
|
584
|
+
col_configs : dict
|
|
585
|
+
Column configuration dictionary
|
|
586
|
+
preserve_order : bool
|
|
587
|
+
If True, preserve the order of cols. If False, reorder by priority.
|
|
588
|
+
|
|
591
589
|
Returns
|
|
592
590
|
-------
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
Raises
|
|
597
|
-
------
|
|
598
|
-
ValueError
|
|
599
|
-
If invalid column names are provided in selected_columns.
|
|
591
|
+
list
|
|
592
|
+
Columns that fit in the terminal, in appropriate order
|
|
600
593
|
"""
|
|
601
|
-
|
|
594
|
+
if len(cols) == 0:
|
|
595
|
+
return cols
|
|
596
|
+
|
|
597
|
+
if preserve_order:
|
|
598
|
+
# User explicitly specified column order - preserve it
|
|
599
|
+
result = []
|
|
600
|
+
for col in cols:
|
|
601
|
+
test_list = result + [col]
|
|
602
|
+
width = _calculate_table_width(test_list, col_configs)
|
|
603
|
+
if width <= terminal_w:
|
|
604
|
+
result.append(col)
|
|
605
|
+
# Continue evaluating remaining columns even if this one doesn't fit
|
|
606
|
+
|
|
607
|
+
# Ensure at least one column is shown, even if terminal is very narrow
|
|
608
|
+
if len(result) == 0 and len(cols) > 0:
|
|
609
|
+
# Find the narrowest column that was requested
|
|
610
|
+
narrowest_col = min(cols, key=lambda c: col_configs[c].get('max_width', col_configs[c].get('min_width', 10)))
|
|
611
|
+
result.append(narrowest_col)
|
|
612
|
+
|
|
613
|
+
return result
|
|
614
|
+
|
|
615
|
+
# Auto-selection mode: reorder by priority for better UX
|
|
616
|
+
essential_requested = [col for col in ESSENTIAL_COLUMN_PRIORITY if col in cols]
|
|
617
|
+
additional_requested = [col for col in cols if col not in ESSENTIAL_COLUMN_PRIORITY]
|
|
618
|
+
|
|
619
|
+
additional_ordered = [col for col in ADDITIONAL_COLUMN_PRIORITY if col in additional_requested]
|
|
620
|
+
additional_ordered.extend([col for col in additional_requested if col not in ADDITIONAL_COLUMN_PRIORITY])
|
|
621
|
+
|
|
622
|
+
result = []
|
|
623
|
+
for col in essential_requested:
|
|
624
|
+
test_list = result + [col]
|
|
625
|
+
width = _calculate_table_width(test_list, col_configs)
|
|
626
|
+
if width <= terminal_w:
|
|
627
|
+
result.append(col)
|
|
628
|
+
else:
|
|
629
|
+
# Column doesn't fit - continue trying remaining columns
|
|
630
|
+
# Special case: always show at least status on very narrow terminals
|
|
631
|
+
if len(result) == 0 and col == 'status':
|
|
632
|
+
result.append(col)
|
|
633
|
+
|
|
634
|
+
# Try to add additional columns one by one
|
|
635
|
+
for col in additional_ordered:
|
|
636
|
+
test_list = result + [col]
|
|
637
|
+
width = _calculate_table_width(test_list, col_configs)
|
|
638
|
+
if width <= terminal_w:
|
|
639
|
+
result.append(col)
|
|
640
|
+
# Continue trying remaining columns even if this one doesn't fit
|
|
641
|
+
|
|
642
|
+
return result
|
|
643
|
+
|
|
602
644
|
|
|
645
|
+
def create_job_list_table(jobs, cloudos_url, pagination_metadata=None, selected_columns=None, fetch_page_callback=None):
|
|
646
|
+
"""Creates a formatted job list table with responsive design and pagination."""
|
|
603
647
|
# Get terminal width for responsive design
|
|
604
648
|
try:
|
|
605
649
|
terminal_width = os.get_terminal_size().columns
|
|
606
650
|
except OSError:
|
|
607
651
|
terminal_width = 80 # Default fallback
|
|
608
652
|
|
|
609
|
-
# Define column priority groups for small terminals
|
|
610
|
-
priority_columns = {
|
|
611
|
-
'essential': ['status', 'name', 'pipeline', 'id'], # ~40 chars minimum
|
|
612
|
-
'important': ['project', 'owner', 'run_time', 'cost'], # +30 chars
|
|
613
|
-
'useful': [ 'submit_time', 'end_time', 'commit'], # +50 chars
|
|
614
|
-
'extended': [ 'resources', 'storage_type'] # +30 chars
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
# Define all available columns with their configurations
|
|
618
|
-
all_columns = {
|
|
619
|
-
'status': {"header": "Status", "style": "cyan", "no_wrap": True, "min_width": 6, "max_width": 6},
|
|
620
|
-
'name': {"header": "Name", "style": "green", "overflow": "ellipsis", "min_width": 6, "max_width": 20},
|
|
621
|
-
'project': {"header": "Project", "style": "magenta", "overflow": "ellipsis", "min_width": 6, "max_width": 15},
|
|
622
|
-
'owner': {"header": "Owner", "style": "blue", "overflow": "ellipsis", "min_width": 6, "max_width": 12},
|
|
623
|
-
'pipeline': {"header": "Pipeline", "style": "yellow", "overflow": "ellipsis", "min_width": 6, "max_width": 15},
|
|
624
|
-
'id': {"header": "ID", "style": "white", "overflow": "ellipsis", "min_width": 6, "max_width": 12},
|
|
625
|
-
'submit_time': {"header": "Submit", "style": "cyan", "no_wrap": True, "min_width": 10, "max_width": 16},
|
|
626
|
-
'end_time': {"header": "End", "style": "cyan", "no_wrap": True, "min_width": 10, "max_width": 16},
|
|
627
|
-
'run_time': {"header": "Runtime", "style": "green", "no_wrap": True, "min_width": 5, "max_width": 10},
|
|
628
|
-
'commit': {"header": "Commit", "style": "magenta", "no_wrap": True, "min_width": 7, "max_width": 8},
|
|
629
|
-
'cost': {"header": "Cost", "style": "yellow", "no_wrap": True, "min_width": 6, "max_width": 10},
|
|
630
|
-
'resources': {"header": "Resources", "style": "blue", "overflow": "ellipsis", "min_width": 3, "max_width": 15},
|
|
631
|
-
'storage_type': {"header": "Storage", "style": "white", "no_wrap": True, "min_width": 3, "max_width": 8}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
# Validate and process selected_columns
|
|
635
653
|
if selected_columns is None:
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
654
|
+
if terminal_width < 80:
|
|
655
|
+
columns_to_show = COLUMN_PRIORITY_GROUPS['minimal']
|
|
656
|
+
elif terminal_width <= 100:
|
|
657
|
+
columns_to_show = COLUMN_PRIORITY_GROUPS['essential']
|
|
658
|
+
elif terminal_width < 150:
|
|
659
|
+
columns_to_show = COLUMN_PRIORITY_GROUPS['essential'] + COLUMN_PRIORITY_GROUPS['important']
|
|
660
|
+
elif terminal_width < 180:
|
|
661
|
+
columns_to_show = (COLUMN_PRIORITY_GROUPS['essential'] +
|
|
662
|
+
COLUMN_PRIORITY_GROUPS['important'] +
|
|
663
|
+
COLUMN_PRIORITY_GROUPS['useful'])
|
|
664
|
+
else:
|
|
665
|
+
columns_to_show = (COLUMN_PRIORITY_GROUPS['essential'] +
|
|
666
|
+
COLUMN_PRIORITY_GROUPS['important'] +
|
|
667
|
+
COLUMN_PRIORITY_GROUPS['useful'] +
|
|
668
|
+
COLUMN_PRIORITY_GROUPS['extended'])
|
|
650
669
|
else:
|
|
651
|
-
# Accept either a comma-separated string or a list
|
|
652
670
|
if isinstance(selected_columns, str):
|
|
653
671
|
selected_columns = [col.strip().lower() for col in selected_columns.split(',')]
|
|
654
|
-
|
|
672
|
+
|
|
673
|
+
# Store original count before deduplication (for accurate warning message)
|
|
674
|
+
original_column_count = len(selected_columns)
|
|
675
|
+
|
|
676
|
+
# Check for duplicates
|
|
677
|
+
duplicates = [col for col in selected_columns if selected_columns.count(col) > 1]
|
|
678
|
+
if duplicates:
|
|
679
|
+
# Deduplicate while preserving order
|
|
680
|
+
seen = set()
|
|
681
|
+
deduplicated = []
|
|
682
|
+
for col in selected_columns:
|
|
683
|
+
if col not in seen:
|
|
684
|
+
seen.add(col)
|
|
685
|
+
deduplicated.append(col)
|
|
686
|
+
selected_columns = deduplicated
|
|
687
|
+
|
|
688
|
+
# Warn user about deduplication
|
|
689
|
+
unique_duplicates = list(dict.fromkeys(duplicates)) # Remove duplicates from duplicates list
|
|
690
|
+
console = Console()
|
|
691
|
+
console.print(f"[yellow]Warning: Duplicate columns removed: {', '.join(unique_duplicates)}[/yellow]")
|
|
692
|
+
|
|
693
|
+
valid_columns = list(COLUMN_CONFIGS.keys())
|
|
655
694
|
invalid_cols = [col for col in selected_columns if col not in valid_columns]
|
|
656
695
|
if invalid_cols:
|
|
657
696
|
raise ValueError(f"Invalid column names: {', '.join(invalid_cols)}. "
|
|
658
697
|
f"Valid columns are: {', '.join(valid_columns)}")
|
|
659
|
-
columns_to_show = selected_columns
|
|
698
|
+
columns_to_show = selected_columns
|
|
699
|
+
|
|
700
|
+
effective_width = terminal_width - 5
|
|
701
|
+
console = Console(width=terminal_width)
|
|
702
|
+
# Preserve user-specified column order; auto-selected columns are reordered by priority
|
|
703
|
+
preserve_order = selected_columns is not None
|
|
704
|
+
columns_to_show = _fit_columns_to_terminal(columns_to_show, effective_width, COLUMN_CONFIGS, preserve_order)
|
|
705
|
+
|
|
706
|
+
# Warn if user-requested columns were truncated due to narrow terminal
|
|
707
|
+
if preserve_order and selected_columns:
|
|
708
|
+
# Use original count before deduplication for accurate message
|
|
709
|
+
original_count = original_column_count
|
|
710
|
+
if len(columns_to_show) < original_count:
|
|
711
|
+
console.print(f"[yellow]Warning: Terminal too narrow. Showing {len(columns_to_show)} of {original_count} requested columns.[/yellow]")
|
|
712
|
+
console.print(f"[yellow]Increase terminal width to see all columns.[/yellow]\n")
|
|
660
713
|
|
|
661
714
|
if not jobs:
|
|
662
715
|
console.print("\n[yellow]No jobs found matching the criteria.[/yellow]")
|
|
663
716
|
return
|
|
664
717
|
|
|
665
|
-
#
|
|
666
|
-
table = _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show,
|
|
718
|
+
# Use actual terminal_width (not effective_width) for date formatting logic
|
|
719
|
+
table = _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, COLUMN_CONFIGS)
|
|
667
720
|
|
|
668
|
-
# If no fetch_page_callback, display static table
|
|
669
721
|
if not fetch_page_callback or not pagination_metadata:
|
|
670
722
|
console.print(table)
|
|
671
|
-
|
|
672
|
-
|
|
723
|
+
legend = _create_status_legend()
|
|
724
|
+
console.print(f"\n{legend}\n")
|
|
725
|
+
|
|
673
726
|
if pagination_metadata:
|
|
674
727
|
total_jobs = pagination_metadata.get('Pagination-Count', 0)
|
|
675
728
|
current_page = pagination_metadata.get('Pagination-Page', 1)
|
|
@@ -679,20 +732,17 @@ def create_job_list_table(jobs, cloudos_url, pagination_metadata=None, selected_
|
|
|
679
732
|
console.print(f"\n[cyan]Showing {len(jobs)} of {total_jobs} total jobs | Page {current_page} of {total_pages}[/cyan]")
|
|
680
733
|
return
|
|
681
734
|
|
|
682
|
-
|
|
683
|
-
current_page = pagination_metadata.get('Pagination-Page', 1) or 1 # Ensure never None
|
|
735
|
+
current_page = pagination_metadata.get('Pagination-Page', 1) or 1
|
|
684
736
|
total_jobs = pagination_metadata.get('Pagination-Count', 0)
|
|
685
737
|
page_size_value = pagination_metadata.get('Pagination-Limit', 10)
|
|
686
738
|
total_pages = (total_jobs + page_size_value - 1) // page_size_value if total_jobs > 0 else 1
|
|
687
|
-
|
|
688
739
|
show_error = None
|
|
689
|
-
|
|
740
|
+
|
|
690
741
|
while True:
|
|
691
|
-
# Clear console and display table
|
|
692
742
|
console.clear()
|
|
693
743
|
console.print(table)
|
|
694
|
-
|
|
695
|
-
|
|
744
|
+
legend = _create_status_legend()
|
|
745
|
+
console.print(f"{legend}\n")
|
|
696
746
|
console.print(f"\n[cyan]Total jobs:[/cyan] {total_jobs}")
|
|
697
747
|
if total_pages > 1:
|
|
698
748
|
console.print(f"[cyan]Page:[/cyan] {current_page} of {total_pages}")
|
|
@@ -705,21 +755,19 @@ def create_job_list_table(jobs, cloudos_url, pagination_metadata=None, selected_
|
|
|
705
755
|
|
|
706
756
|
# Show pagination controls only if there are multiple pages
|
|
707
757
|
if total_pages > 1:
|
|
708
|
-
# Check if we're in an interactive environment
|
|
709
758
|
if not sys.stdin.isatty():
|
|
710
759
|
console.print("\n[yellow]Note: Pagination not available in non-interactive mode. Showing page 1 of {0}.[/yellow]".format(total_pages))
|
|
711
760
|
console.print("[yellow]Run in an interactive terminal to navigate through all pages.[/yellow]")
|
|
712
761
|
break
|
|
713
|
-
|
|
762
|
+
|
|
714
763
|
console.print(f"\n[bold cyan]n[/] = next, [bold cyan]p[/] = prev, [bold cyan]q[/] = quit")
|
|
715
|
-
|
|
716
|
-
# Get user input for navigation
|
|
764
|
+
|
|
717
765
|
try:
|
|
718
766
|
choice = input(">>> ").strip().lower()
|
|
719
767
|
except (EOFError, KeyboardInterrupt):
|
|
720
768
|
console.print("\n[yellow]Pagination interrupted.[/yellow]")
|
|
721
769
|
break
|
|
722
|
-
|
|
770
|
+
|
|
723
771
|
if choice in ("q", "quit"):
|
|
724
772
|
break
|
|
725
773
|
elif choice in ("n", "next"):
|
|
@@ -732,10 +780,8 @@ def create_job_list_table(jobs, cloudos_url, pagination_metadata=None, selected_
|
|
|
732
780
|
total_pages = pagination_metadata.get('totalPages',
|
|
733
781
|
(pagination_metadata.get('Pagination-Count', 0) + page_size_value - 1) // page_size_value
|
|
734
782
|
if pagination_metadata.get('Pagination-Count', 0) > 0 else 1)
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
table = _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, all_columns)
|
|
738
|
-
|
|
783
|
+
# Use terminal_width (not effective_width) for consistent date formatting
|
|
784
|
+
table = _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, COLUMN_CONFIGS)
|
|
739
785
|
except Exception as e:
|
|
740
786
|
show_error = f"[red]Error fetching page: {str(e)}[/red]"
|
|
741
787
|
else:
|
|
@@ -750,10 +796,8 @@ def create_job_list_table(jobs, cloudos_url, pagination_metadata=None, selected_
|
|
|
750
796
|
total_pages = pagination_metadata.get('totalPages',
|
|
751
797
|
(pagination_metadata.get('Pagination-Count', 0) + page_size_value - 1) // page_size_value
|
|
752
798
|
if pagination_metadata.get('Pagination-Count', 0) > 0 else 1)
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
table = _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, all_columns)
|
|
756
|
-
|
|
799
|
+
# Use terminal_width (not effective_width) for consistent date formatting
|
|
800
|
+
table = _build_job_table(jobs, cloudos_url, terminal_width, columns_to_show, COLUMN_CONFIGS)
|
|
757
801
|
except Exception as e:
|
|
758
802
|
show_error = f"[red]Error fetching page: {str(e)}[/red]"
|
|
759
803
|
else:
|
|
@@ -761,7 +805,6 @@ def create_job_list_table(jobs, cloudos_url, pagination_metadata=None, selected_
|
|
|
761
805
|
else:
|
|
762
806
|
show_error = "[yellow]Invalid choice. Use 'n' (next), 'p' (previous), or 'q' (quit)[/yellow]"
|
|
763
807
|
else:
|
|
764
|
-
# Only one page, exit after displaying
|
|
765
808
|
break
|
|
766
809
|
|
|
767
810
|
|