synapse-sdk 1.0.0a23__py3-none-any.whl → 2025.12.3__py3-none-any.whl
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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +310 -5
- synapse_sdk/cli/alias/__init__.py +22 -0
- synapse_sdk/cli/alias/create.py +36 -0
- synapse_sdk/cli/alias/dataclass.py +31 -0
- synapse_sdk/cli/alias/default.py +16 -0
- synapse_sdk/cli/alias/delete.py +15 -0
- synapse_sdk/cli/alias/list.py +19 -0
- synapse_sdk/cli/alias/read.py +15 -0
- synapse_sdk/cli/alias/update.py +17 -0
- synapse_sdk/cli/alias/utils.py +61 -0
- synapse_sdk/cli/code_server.py +687 -0
- synapse_sdk/cli/config.py +440 -0
- synapse_sdk/cli/devtools.py +90 -0
- synapse_sdk/cli/plugin/__init__.py +33 -0
- synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
- synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
- synapse_sdk/clients/agent/__init__.py +9 -3
- synapse_sdk/clients/agent/container.py +143 -0
- synapse_sdk/clients/agent/core.py +19 -0
- synapse_sdk/clients/agent/ray.py +298 -9
- synapse_sdk/clients/backend/__init__.py +30 -12
- synapse_sdk/clients/backend/annotation.py +13 -5
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +186 -0
- synapse_sdk/clients/backend/hitl.py +17 -0
- synapse_sdk/clients/backend/integration.py +16 -1
- synapse_sdk/clients/backend/ml.py +5 -1
- synapse_sdk/clients/backend/models.py +78 -0
- synapse_sdk/clients/base.py +384 -41
- synapse_sdk/clients/ray/serve.py +2 -0
- synapse_sdk/clients/validators/collections.py +31 -0
- synapse_sdk/devtools/config.py +94 -0
- synapse_sdk/devtools/server.py +41 -0
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/loggers.py +120 -9
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/__init__.py +0 -13
- synapse_sdk/plugins/categories/base.py +117 -11
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
- synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
- synapse_sdk/plugins/categories/upload/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
- synapse_sdk/plugins/enums.py +3 -1
- synapse_sdk/plugins/models.py +148 -11
- synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
- synapse_sdk/plugins/templates/schema.json +491 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
- synapse_sdk/plugins/utils/__init__.py +46 -0
- synapse_sdk/plugins/utils/actions.py +119 -0
- synapse_sdk/plugins/utils/config.py +203 -0
- synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/plugins/utils/registry.py +58 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/shared/enums.py +93 -0
- synapse_sdk/types.py +19 -0
- synapse_sdk/utils/converters/__init__.py +240 -0
- synapse_sdk/utils/converters/coco/__init__.py +0 -0
- synapse_sdk/utils/converters/coco/from_dm.py +322 -0
- synapse_sdk/utils/converters/coco/to_dm.py +215 -0
- synapse_sdk/utils/converters/dm/__init__.py +57 -0
- synapse_sdk/utils/converters/dm/base.py +137 -0
- synapse_sdk/utils/converters/dm/from_v1.py +273 -0
- synapse_sdk/utils/converters/dm/to_v1.py +321 -0
- synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
- synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
- synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
- synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
- synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
- synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
- synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
- synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
- synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
- synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
- synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
- synapse_sdk/utils/converters/dm/types.py +168 -0
- synapse_sdk/utils/converters/dm/utils.py +162 -0
- synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
- synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
- synapse_sdk/utils/converters/pascal/__init__.py +0 -0
- synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
- synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
- synapse_sdk/utils/converters/yolo/__init__.py +0 -0
- synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
- synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
- synapse_sdk/utils/dataset.py +46 -0
- synapse_sdk/utils/encryption.py +158 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/upload.py +165 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/file.py.backup +301 -0
- synapse_sdk/utils/http.py +138 -0
- synapse_sdk/utils/network.py +309 -0
- synapse_sdk/utils/storage/__init__.py +72 -0
- synapse_sdk/utils/storage/providers/__init__.py +183 -0
- synapse_sdk/utils/storage/providers/file_system.py +134 -0
- synapse_sdk/utils/storage/providers/gcp.py +13 -0
- synapse_sdk/utils/storage/providers/http.py +190 -0
- synapse_sdk/utils/storage/providers/s3.py +91 -0
- synapse_sdk/utils/storage/providers/sftp.py +47 -0
- synapse_sdk/utils/storage/registry.py +17 -0
- synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
- synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/utils/file.py +0 -168
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
- synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
- /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
- /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
- /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
"""Jobs tab UI component."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import traceback
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
import streamlit as st
|
|
8
|
+
|
|
9
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
10
|
+
|
|
11
|
+
from ..services.job_service import JobService
|
|
12
|
+
from ..utils.json_viewer import render_json_as_table, render_json_compact, render_metrics_grid
|
|
13
|
+
from ..utils.log_formatter import format_log_line
|
|
14
|
+
from ..utils.styles import get_status_badge_html
|
|
15
|
+
from ..utils.ui_components import (
|
|
16
|
+
render_action_button,
|
|
17
|
+
render_empty_state,
|
|
18
|
+
render_section_header,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JobsTab:
|
|
23
|
+
"""UI component for the Jobs tab."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, job_service: JobService, agent_id: Optional[int] = None, agent_info: Optional[Dict] = None):
|
|
26
|
+
self.job_service = job_service
|
|
27
|
+
self.agent_id = agent_id
|
|
28
|
+
self.agent_info = agent_info or {}
|
|
29
|
+
|
|
30
|
+
def render(self):
|
|
31
|
+
"""Render the Jobs tab."""
|
|
32
|
+
if not self.job_service.backend_client:
|
|
33
|
+
st.error('Backend client not configured. Please check your backend configuration.')
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
# Check if we should show job details
|
|
37
|
+
if 'selected_job' in st.session_state:
|
|
38
|
+
self.render_job_details(st.session_state['selected_job'])
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# Controls row
|
|
42
|
+
col1, col2, col3 = st.columns([1, 1, 2])
|
|
43
|
+
with col1:
|
|
44
|
+
if render_action_button('Refresh', key='refresh_jobs', icon='🔄', type='secondary'):
|
|
45
|
+
st.rerun()
|
|
46
|
+
|
|
47
|
+
with col2:
|
|
48
|
+
auto_refresh = st.checkbox('Auto-refresh', value=False, key='jobs_auto_refresh')
|
|
49
|
+
|
|
50
|
+
with col3:
|
|
51
|
+
if auto_refresh:
|
|
52
|
+
refresh_interval = st.selectbox(
|
|
53
|
+
'Refresh interval',
|
|
54
|
+
options=[2, 5, 10, 30],
|
|
55
|
+
index=1, # Default to 5 seconds
|
|
56
|
+
format_func=lambda x: f'{x}s',
|
|
57
|
+
key='jobs_refresh_interval',
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Get page size from session state or use default
|
|
61
|
+
page_size = st.session_state.get('page_size_value', 20)
|
|
62
|
+
|
|
63
|
+
# Initialize pagination state
|
|
64
|
+
if 'current_page' not in st.session_state:
|
|
65
|
+
st.session_state.current_page = 0
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
valid_jobs = self.job_service.list_jobs(self.agent_id, self.agent_info)
|
|
69
|
+
|
|
70
|
+
# Pagination logic
|
|
71
|
+
total_jobs = len(valid_jobs)
|
|
72
|
+
total_pages = (total_jobs + page_size - 1) // page_size if total_jobs > 0 else 1
|
|
73
|
+
|
|
74
|
+
# Ensure current page is valid
|
|
75
|
+
if st.session_state.current_page >= total_pages:
|
|
76
|
+
st.session_state.current_page = max(0, total_pages - 1)
|
|
77
|
+
|
|
78
|
+
start_idx = st.session_state.current_page * page_size
|
|
79
|
+
end_idx = min(start_idx + page_size, total_jobs)
|
|
80
|
+
page_jobs = valid_jobs[start_idx:end_idx]
|
|
81
|
+
|
|
82
|
+
# Display job count info
|
|
83
|
+
if total_jobs > 0:
|
|
84
|
+
st.success(f'Showing {start_idx + 1}-{end_idx} of {total_jobs} jobs')
|
|
85
|
+
else:
|
|
86
|
+
render_empty_state(
|
|
87
|
+
'No jobs found', icon='📋', action_label='Refresh', action_callback=lambda: st.rerun()
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Display jobs in table format
|
|
92
|
+
if page_jobs:
|
|
93
|
+
self._render_jobs_table(page_jobs)
|
|
94
|
+
|
|
95
|
+
# Pagination controls at the bottom
|
|
96
|
+
if total_pages > 1 or total_jobs > 10:
|
|
97
|
+
self._render_pagination_controls(total_pages, page_size)
|
|
98
|
+
|
|
99
|
+
except ClientError as e:
|
|
100
|
+
st.error(f'Failed to fetch jobs: {e.reason}')
|
|
101
|
+
except Exception as e:
|
|
102
|
+
st.error(f'Unexpected error: {e}')
|
|
103
|
+
st.code(traceback.format_exc())
|
|
104
|
+
|
|
105
|
+
# Auto-refresh implementation
|
|
106
|
+
if auto_refresh:
|
|
107
|
+
refresh_interval = st.session_state.get('jobs_refresh_interval', 5)
|
|
108
|
+
placeholder = st.empty()
|
|
109
|
+
with placeholder.container():
|
|
110
|
+
st.info(f'Refreshing in {refresh_interval} seconds...')
|
|
111
|
+
time.sleep(refresh_interval)
|
|
112
|
+
placeholder.empty()
|
|
113
|
+
st.rerun()
|
|
114
|
+
|
|
115
|
+
def _render_jobs_table(self, jobs):
|
|
116
|
+
"""Render the jobs table."""
|
|
117
|
+
# Table header with balanced spacing
|
|
118
|
+
header_cols = st.columns([2, 1.2, 1.5, 1, 0.8, 1, 0.6, 1.2])
|
|
119
|
+
headers = ['Job', 'Plugin', 'User', 'Status', 'Progress', 'Duration', '', 'Created']
|
|
120
|
+
|
|
121
|
+
for i, header in enumerate(headers):
|
|
122
|
+
with header_cols[i]:
|
|
123
|
+
st.markdown(
|
|
124
|
+
f"<div style='color:#666; font-size:13px; font-weight:600; padding: 4px 0;'>{header}</div>",
|
|
125
|
+
unsafe_allow_html=True,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
st.markdown("<hr style='margin: 8px 0; border: none; border-top: 1px solid #e0e0e0'>", unsafe_allow_html=True)
|
|
129
|
+
|
|
130
|
+
# Table rows with balanced spacing
|
|
131
|
+
for job in jobs:
|
|
132
|
+
cols = st.columns([2, 1.2, 1.5, 1, 0.8, 1, 0.6, 1.2])
|
|
133
|
+
|
|
134
|
+
# Job Name/ID
|
|
135
|
+
with cols[0]:
|
|
136
|
+
job_id = job.get('id', 'N/A')
|
|
137
|
+
# Try to get job name from various fields
|
|
138
|
+
job_name = (
|
|
139
|
+
job.get('name')
|
|
140
|
+
or job.get('display_name')
|
|
141
|
+
or job.get('action')
|
|
142
|
+
or (job.get('params', {}).get('name') if isinstance(job.get('params'), dict) else None)
|
|
143
|
+
or f'Job {str(job_id)[:8]}...'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Display name with ID as subtitle
|
|
147
|
+
st.markdown(
|
|
148
|
+
f"<div style='font-size:13px; padding: 6px 0;'>"
|
|
149
|
+
f"<div style='font-weight: 500;'>{job_name}</div>"
|
|
150
|
+
f"<div style='font-size: 11px; color: #666; font-family: monospace;'>{str(job_id)[:8]}...</div>"
|
|
151
|
+
f'</div>',
|
|
152
|
+
unsafe_allow_html=True,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Plugin Category
|
|
156
|
+
with cols[1]:
|
|
157
|
+
plugin_category = job.get('plugin_category', 'N/A')
|
|
158
|
+
st.markdown(
|
|
159
|
+
f"<div style='font-size:13px; padding: 6px 0;'>{plugin_category}</div>",
|
|
160
|
+
unsafe_allow_html=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# User
|
|
164
|
+
with cols[2]:
|
|
165
|
+
user = job.get('user', {})
|
|
166
|
+
user_name = user.get('name', user.get('email', 'N/A'))
|
|
167
|
+
display_name = user_name[:12] + '...' if len(str(user_name)) > 12 else str(user_name)
|
|
168
|
+
st.markdown(
|
|
169
|
+
f"<div style='font-size:13px; padding: 6px 0;'>{display_name}</div>", unsafe_allow_html=True
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Status with badge
|
|
173
|
+
with cols[3]:
|
|
174
|
+
status = job.get('status', 'Unknown')
|
|
175
|
+
st.markdown(get_status_badge_html(status), unsafe_allow_html=True)
|
|
176
|
+
|
|
177
|
+
# Progress
|
|
178
|
+
with cols[4]:
|
|
179
|
+
progress = job.get('progress', {})
|
|
180
|
+
overall = progress.get('overall', 0)
|
|
181
|
+
if overall is not None:
|
|
182
|
+
st.markdown(
|
|
183
|
+
f"<div style='font-size:13px; padding: 6px 0;'>{overall}%</div>", unsafe_allow_html=True
|
|
184
|
+
)
|
|
185
|
+
else:
|
|
186
|
+
st.markdown("<div style='font-size:13px; padding: 6px 0;'>-</div>", unsafe_allow_html=True)
|
|
187
|
+
|
|
188
|
+
# Duration
|
|
189
|
+
with cols[5]:
|
|
190
|
+
duration_text = self.job_service.format_duration(job.get('created'), job.get('completed'))
|
|
191
|
+
st.markdown(
|
|
192
|
+
f"<div style='font-size:13px; padding: 6px 0;'>{duration_text}</div>",
|
|
193
|
+
unsafe_allow_html=True,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Actions
|
|
197
|
+
with cols[6]:
|
|
198
|
+
logs_key = f'logs_{job.get("id")}_{hash(str(job))}'
|
|
199
|
+
|
|
200
|
+
def select_job(job):
|
|
201
|
+
st.session_state['selected_job'] = job
|
|
202
|
+
|
|
203
|
+
# Compact view button
|
|
204
|
+
if render_action_button(
|
|
205
|
+
'', key=logs_key, icon='→', type='minimal', on_click=select_job, args=(job,), help='View details'
|
|
206
|
+
):
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
# Start Time
|
|
210
|
+
with cols[7]:
|
|
211
|
+
time_str = self.job_service.format_timestamp(job.get('created'))
|
|
212
|
+
st.markdown(
|
|
213
|
+
f"<div style='font-size:13px; padding: 6px 0;'>{time_str}</div>",
|
|
214
|
+
unsafe_allow_html=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
st.markdown(
|
|
218
|
+
"<hr style='margin: 6px 0; border: none; border-top: 1px solid #f0f0f0'>",
|
|
219
|
+
unsafe_allow_html=True,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _render_pagination_controls(self, total_pages, page_size):
|
|
223
|
+
"""Render pagination controls."""
|
|
224
|
+
st.markdown('---')
|
|
225
|
+
col1, col2, col3, col4, col5, col6 = st.columns([1, 1, 1.5, 1, 1, 1.5])
|
|
226
|
+
|
|
227
|
+
with col1:
|
|
228
|
+
if render_action_button(
|
|
229
|
+
'First', key='bottom_first', type='minimal', disabled=st.session_state.current_page == 0
|
|
230
|
+
):
|
|
231
|
+
st.session_state.current_page = 0
|
|
232
|
+
st.rerun()
|
|
233
|
+
|
|
234
|
+
with col2:
|
|
235
|
+
if render_action_button(
|
|
236
|
+
'Prev', key='bottom_prev', type='minimal', disabled=st.session_state.current_page == 0
|
|
237
|
+
):
|
|
238
|
+
st.session_state.current_page -= 1
|
|
239
|
+
st.rerun()
|
|
240
|
+
|
|
241
|
+
with col3:
|
|
242
|
+
st.markdown(
|
|
243
|
+
f'<div style="text-align: center; padding: 8px; color: #495057; font-size: 14px;">'
|
|
244
|
+
f'Page {st.session_state.current_page + 1} of {total_pages}</div>',
|
|
245
|
+
unsafe_allow_html=True,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
with col4:
|
|
249
|
+
if render_action_button(
|
|
250
|
+
'Next', key='bottom_next', type='minimal', disabled=st.session_state.current_page >= total_pages - 1
|
|
251
|
+
):
|
|
252
|
+
st.session_state.current_page += 1
|
|
253
|
+
st.rerun()
|
|
254
|
+
|
|
255
|
+
with col5:
|
|
256
|
+
if render_action_button(
|
|
257
|
+
'Last', key='bottom_last', type='minimal', disabled=st.session_state.current_page >= total_pages - 1
|
|
258
|
+
):
|
|
259
|
+
st.session_state.current_page = total_pages - 1
|
|
260
|
+
st.rerun()
|
|
261
|
+
|
|
262
|
+
with col6:
|
|
263
|
+
new_page_size = st.selectbox(
|
|
264
|
+
'Items per page',
|
|
265
|
+
options=[10, 20, 50, 100],
|
|
266
|
+
index=[10, 20, 50, 100].index(page_size) if page_size in [10, 20, 50, 100] else 1,
|
|
267
|
+
key='page_size_selector',
|
|
268
|
+
)
|
|
269
|
+
if new_page_size != page_size:
|
|
270
|
+
st.session_state.page_size_value = new_page_size
|
|
271
|
+
st.session_state.current_page = 0 # Reset to first page
|
|
272
|
+
st.rerun()
|
|
273
|
+
|
|
274
|
+
def render_job_details(self, job):
|
|
275
|
+
"""Render the job details page."""
|
|
276
|
+
job_id = job.get('id', 'Unknown')
|
|
277
|
+
|
|
278
|
+
# Check if we're viewing a different job and clear cached logs
|
|
279
|
+
if 'last_viewed_job_id' not in st.session_state or st.session_state['last_viewed_job_id'] != job_id:
|
|
280
|
+
# Clear cached logs and streaming state when switching to a different job
|
|
281
|
+
st.session_state.pop('job_logs_content', None)
|
|
282
|
+
st.session_state.pop('stream_counter', None)
|
|
283
|
+
st.session_state['last_viewed_job_id'] = job_id
|
|
284
|
+
|
|
285
|
+
# Try to get job name
|
|
286
|
+
job_name = (
|
|
287
|
+
job.get('name')
|
|
288
|
+
or job.get('display_name')
|
|
289
|
+
or job.get('action')
|
|
290
|
+
or (job.get('params', {}).get('name') if isinstance(job.get('params'), dict) else None)
|
|
291
|
+
or f'Job {str(job_id)[:8]}...'
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Header with back button
|
|
295
|
+
col1, col2 = st.columns([1, 4])
|
|
296
|
+
with col1:
|
|
297
|
+
if render_action_button('Back', key='back_to_jobs', icon='←', type='secondary'):
|
|
298
|
+
del st.session_state['selected_job']
|
|
299
|
+
st.rerun()
|
|
300
|
+
with col2:
|
|
301
|
+
render_section_header(job_name, f'ID: {job_id}')
|
|
302
|
+
|
|
303
|
+
# Status badge with spacing
|
|
304
|
+
status = job.get('status', 'Unknown')
|
|
305
|
+
st.markdown(f"<div style='margin: 12px 0;'>{get_status_badge_html(status)}</div>", unsafe_allow_html=True)
|
|
306
|
+
|
|
307
|
+
# Job Logs section (moved to top)
|
|
308
|
+
render_section_header('Job Logs')
|
|
309
|
+
|
|
310
|
+
# Add controls for logs
|
|
311
|
+
col1, col2, col3 = st.columns([1, 1, 2])
|
|
312
|
+
with col1:
|
|
313
|
+
st.checkbox(
|
|
314
|
+
'Auto-scroll', value=True, key='logs_auto_scroll', disabled=True, help='Always scrolls to bottom'
|
|
315
|
+
)
|
|
316
|
+
with col2:
|
|
317
|
+
stream_logs = st.checkbox(
|
|
318
|
+
'Auto-refresh', value=False, key='logs_stream', help='Refresh logs every 3 seconds'
|
|
319
|
+
)
|
|
320
|
+
with col3:
|
|
321
|
+
if st.button('Clear cache', key='clear_logs'):
|
|
322
|
+
st.session_state.pop('job_logs_content', None)
|
|
323
|
+
|
|
324
|
+
# Create log display area with CSS
|
|
325
|
+
log_css = """
|
|
326
|
+
<style>
|
|
327
|
+
.log-container {
|
|
328
|
+
background-color: #1e1e1e;
|
|
329
|
+
color: #d4d4d4;
|
|
330
|
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
331
|
+
font-size: 12px;
|
|
332
|
+
padding: 12px;
|
|
333
|
+
border-radius: 4px;
|
|
334
|
+
height: 500px;
|
|
335
|
+
overflow-y: auto;
|
|
336
|
+
overflow-x: auto;
|
|
337
|
+
white-space: pre;
|
|
338
|
+
}
|
|
339
|
+
.log-line {
|
|
340
|
+
display: flex;
|
|
341
|
+
line-height: 1.4;
|
|
342
|
+
padding: 2px 0;
|
|
343
|
+
}
|
|
344
|
+
.log-line:hover {
|
|
345
|
+
background-color: #2d2d2d;
|
|
346
|
+
}
|
|
347
|
+
.log-line-number {
|
|
348
|
+
color: #858585;
|
|
349
|
+
margin-right: 12px;
|
|
350
|
+
min-width: 40px;
|
|
351
|
+
text-align: right;
|
|
352
|
+
user-select: none;
|
|
353
|
+
}
|
|
354
|
+
.log-line-content {
|
|
355
|
+
flex: 1;
|
|
356
|
+
white-space: pre-wrap;
|
|
357
|
+
word-wrap: break-word;
|
|
358
|
+
}
|
|
359
|
+
.log-error {
|
|
360
|
+
color: #f48771;
|
|
361
|
+
}
|
|
362
|
+
.log-warning {
|
|
363
|
+
color: #dcdcaa;
|
|
364
|
+
}
|
|
365
|
+
.log-info {
|
|
366
|
+
color: #9cdcfe;
|
|
367
|
+
}
|
|
368
|
+
.log-debug {
|
|
369
|
+
color: #858585;
|
|
370
|
+
}
|
|
371
|
+
.log-timestamp {
|
|
372
|
+
color: #569cd6;
|
|
373
|
+
}
|
|
374
|
+
</style>
|
|
375
|
+
"""
|
|
376
|
+
st.markdown(log_css, unsafe_allow_html=True)
|
|
377
|
+
|
|
378
|
+
# Display logs container
|
|
379
|
+
log_placeholder = st.empty()
|
|
380
|
+
|
|
381
|
+
# Control buttons
|
|
382
|
+
col1, col2 = st.columns([1, 3])
|
|
383
|
+
with col1:
|
|
384
|
+
if st.button('🔄 Refresh', key='refresh_logs'):
|
|
385
|
+
st.session_state.pop('job_logs_content', None)
|
|
386
|
+
st.rerun()
|
|
387
|
+
|
|
388
|
+
# Auto-refresh using JavaScript
|
|
389
|
+
if stream_logs:
|
|
390
|
+
with col2:
|
|
391
|
+
st.info('🔄 Auto-refresh enabled (every 3 seconds)')
|
|
392
|
+
# Add JavaScript to refresh the page every 3 seconds
|
|
393
|
+
st.markdown(
|
|
394
|
+
"""
|
|
395
|
+
<script>
|
|
396
|
+
setTimeout(function() {
|
|
397
|
+
window.location.reload();
|
|
398
|
+
}, 3000);
|
|
399
|
+
</script>
|
|
400
|
+
""",
|
|
401
|
+
unsafe_allow_html=True,
|
|
402
|
+
)
|
|
403
|
+
# Clear cache for fresh logs on each refresh
|
|
404
|
+
st.session_state.pop('job_logs_content', None)
|
|
405
|
+
|
|
406
|
+
# Display logs
|
|
407
|
+
with log_placeholder.container():
|
|
408
|
+
try:
|
|
409
|
+
# Fetch logs if not cached
|
|
410
|
+
if 'job_logs_content' not in st.session_state:
|
|
411
|
+
with st.spinner('Fetching logs...'):
|
|
412
|
+
logs = []
|
|
413
|
+
for log_line in self.job_service.get_job_logs(job_id):
|
|
414
|
+
logs.append(log_line)
|
|
415
|
+
st.session_state['job_logs_content'] = logs
|
|
416
|
+
|
|
417
|
+
# Get logs from cache
|
|
418
|
+
logs = st.session_state.get('job_logs_content', [])
|
|
419
|
+
|
|
420
|
+
# Display logs
|
|
421
|
+
if logs:
|
|
422
|
+
formatted_logs = []
|
|
423
|
+
for i, line in enumerate(logs, 1):
|
|
424
|
+
formatted_logs.append(format_log_line(line, i))
|
|
425
|
+
|
|
426
|
+
logs_html = f'<div class="log-container" id="logContainer">{"".join(formatted_logs)}</div>'
|
|
427
|
+
st.markdown(logs_html, unsafe_allow_html=True)
|
|
428
|
+
|
|
429
|
+
# Always scroll to bottom when logs are displayed
|
|
430
|
+
st.markdown(
|
|
431
|
+
"""
|
|
432
|
+
<script>
|
|
433
|
+
setTimeout(function() {
|
|
434
|
+
var logContainer = document.getElementById('logContainer');
|
|
435
|
+
if (logContainer) {
|
|
436
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
437
|
+
}
|
|
438
|
+
}, 100);
|
|
439
|
+
</script>
|
|
440
|
+
""",
|
|
441
|
+
unsafe_allow_html=True,
|
|
442
|
+
)
|
|
443
|
+
else:
|
|
444
|
+
st.info('No logs available for this job.')
|
|
445
|
+
|
|
446
|
+
except Exception as e:
|
|
447
|
+
st.error(f'Failed to fetch logs: {e}')
|
|
448
|
+
|
|
449
|
+
# Job Information section with better spacing
|
|
450
|
+
st.markdown("<div style='margin-top: 30px;'></div>", unsafe_allow_html=True)
|
|
451
|
+
render_section_header('Job Information')
|
|
452
|
+
col1, col2 = st.columns(2)
|
|
453
|
+
|
|
454
|
+
with col1:
|
|
455
|
+
st.markdown(
|
|
456
|
+
f"<div style='margin-bottom: 12px;'><strong>Job ID:</strong> <code>{job.get('id', 'N/A')}</code></div>",
|
|
457
|
+
unsafe_allow_html=True,
|
|
458
|
+
)
|
|
459
|
+
st.markdown(
|
|
460
|
+
f"<div style='margin-bottom: 12px;'><strong>Status:</strong> {job.get('status', 'N/A')}</div>",
|
|
461
|
+
unsafe_allow_html=True,
|
|
462
|
+
)
|
|
463
|
+
plugin_cat = job.get('plugin_category', 'N/A')
|
|
464
|
+
st.markdown(
|
|
465
|
+
f"<div style='margin-bottom: 12px;'><strong>Plugin Category:</strong> {plugin_cat}</div>",
|
|
466
|
+
unsafe_allow_html=True,
|
|
467
|
+
)
|
|
468
|
+
# Display agent info with enriched data
|
|
469
|
+
agent_id = job.get('agent', 'N/A')
|
|
470
|
+
agent_name = job.get('agent_name')
|
|
471
|
+
agent_url = job.get('agent_url')
|
|
472
|
+
|
|
473
|
+
if agent_name and agent_url:
|
|
474
|
+
agent_display = f'{agent_name} @ {agent_url}'
|
|
475
|
+
elif agent_name:
|
|
476
|
+
agent_display = agent_name
|
|
477
|
+
elif agent_url:
|
|
478
|
+
agent_display = agent_url
|
|
479
|
+
else:
|
|
480
|
+
agent_display = f'Agent #{agent_id}'
|
|
481
|
+
|
|
482
|
+
st.markdown(
|
|
483
|
+
f"<div style='margin-bottom: 12px;'><strong>Agent:</strong> {agent_display}</div>",
|
|
484
|
+
unsafe_allow_html=True,
|
|
485
|
+
)
|
|
486
|
+
# Display plugin info with enriched data
|
|
487
|
+
plugin_rel = job.get('plugin_release', 'N/A')
|
|
488
|
+
plugin_name = job.get('plugin_name') or job.get('plugin_code')
|
|
489
|
+
plugin_version = job.get('plugin_version')
|
|
490
|
+
|
|
491
|
+
if not plugin_name:
|
|
492
|
+
plugin_name = job.get('plugin_category')
|
|
493
|
+
|
|
494
|
+
if plugin_name and plugin_version:
|
|
495
|
+
plugin_display = f'{plugin_name} (v{plugin_version})'
|
|
496
|
+
elif plugin_name:
|
|
497
|
+
plugin_display = plugin_name
|
|
498
|
+
else:
|
|
499
|
+
plugin_display = f'Plugin #{plugin_rel}'
|
|
500
|
+
|
|
501
|
+
st.markdown(
|
|
502
|
+
f"<div style='margin-bottom: 12px;'><strong>Plugin:</strong> {plugin_display}</div>",
|
|
503
|
+
unsafe_allow_html=True,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
with col2:
|
|
507
|
+
user = job.get('user', {})
|
|
508
|
+
user_name = user.get('name', user.get('email', 'N/A'))
|
|
509
|
+
st.markdown(
|
|
510
|
+
f"<div style='margin-bottom: 12px;'><strong>User:</strong> {user_name}</div>",
|
|
511
|
+
unsafe_allow_html=True,
|
|
512
|
+
)
|
|
513
|
+
st.markdown(
|
|
514
|
+
f"<div style='margin-bottom: 12px;'><strong>Created:</strong> {job.get('created', 'N/A')}</div>",
|
|
515
|
+
unsafe_allow_html=True,
|
|
516
|
+
)
|
|
517
|
+
completed = job.get('completed', 'N/A') if job.get('completed') else 'In Progress'
|
|
518
|
+
st.markdown(
|
|
519
|
+
f"<div style='margin-bottom: 12px;'><strong>Completed:</strong> {completed}</div>",
|
|
520
|
+
unsafe_allow_html=True,
|
|
521
|
+
)
|
|
522
|
+
progress = job.get('progress', {})
|
|
523
|
+
st.markdown(
|
|
524
|
+
f"<div style='margin-bottom: 12px;'><strong>Progress:</strong> {progress.get('overall', 0)}%</div>",
|
|
525
|
+
unsafe_allow_html=True,
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Calculate duration
|
|
529
|
+
duration_text = self.job_service.format_duration(job.get('created'), job.get('completed'))
|
|
530
|
+
if duration_text not in ['-', '...']:
|
|
531
|
+
st.markdown(
|
|
532
|
+
f"<div style='margin-bottom: 12px;'><strong>Duration:</strong> {duration_text}</div>",
|
|
533
|
+
unsafe_allow_html=True,
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Parameters section with spacing
|
|
537
|
+
if job.get('params'):
|
|
538
|
+
st.markdown("<div style='margin-top: 30px;'></div>", unsafe_allow_html=True)
|
|
539
|
+
params = job.get('params')
|
|
540
|
+
# Use table view for simple params, compact view for complex
|
|
541
|
+
if isinstance(params, dict) and all(not isinstance(v, (dict, list)) for v in params.values()):
|
|
542
|
+
render_json_as_table(params, 'Parameters')
|
|
543
|
+
else:
|
|
544
|
+
render_json_compact(params, 'Parameters')
|
|
545
|
+
|
|
546
|
+
# Result section with spacing
|
|
547
|
+
if job.get('result'):
|
|
548
|
+
st.markdown("<div style='margin-top: 30px;'></div>", unsafe_allow_html=True)
|
|
549
|
+
render_json_compact(job.get('result'), 'Result')
|
|
550
|
+
|
|
551
|
+
# Metrics section with spacing
|
|
552
|
+
metrics = job.get('metrics_by_categories')
|
|
553
|
+
if metrics and metrics != {}:
|
|
554
|
+
st.markdown("<div style='margin-top: 30px;'></div>", unsafe_allow_html=True)
|
|
555
|
+
# Use grid for simple metrics, compact view for nested
|
|
556
|
+
if isinstance(metrics, dict):
|
|
557
|
+
has_nested = any(isinstance(v, (dict, list)) for v in metrics.values())
|
|
558
|
+
if has_nested:
|
|
559
|
+
render_json_compact(metrics, 'Metrics by Categories')
|
|
560
|
+
else:
|
|
561
|
+
render_metrics_grid(metrics, 'Metrics')
|
|
562
|
+
else:
|
|
563
|
+
render_json_compact(metrics, 'Metrics')
|
|
564
|
+
|
|
565
|
+
# Detail section with spacing
|
|
566
|
+
detail = job.get('detail')
|
|
567
|
+
if detail and detail != {}:
|
|
568
|
+
st.markdown("<div style='margin-top: 30px;'></div>", unsafe_allow_html=True)
|
|
569
|
+
# Use table for simple details, compact for complex
|
|
570
|
+
if isinstance(detail, dict) and all(not isinstance(v, (dict, list)) for v in detail.values()):
|
|
571
|
+
render_json_as_table(detail, 'Additional Details')
|
|
572
|
+
else:
|
|
573
|
+
render_json_compact(detail, 'Additional Details')
|